Compare commits

...

22 Commits

Author SHA1 Message Date
Gerzon Z a305676fd6 Added deleteTeam function 2021-05-07 08:01:27 -04:00
Gerzon Z 9cf3e68721 Merge branch 'new.delete-team' into new.add-remove-users-teams 2021-05-06 09:07:53 -04:00
Gerzon Z 04ea01b324 Minor tweak 2021-05-06 09:05:11 -04:00
Gerzon Z b961fa05d9 Added addTeamMember and removeTeamMember 2021-05-06 08:57:53 -04:00
Gerzon Z 4b6e691d8a Added SelectListView and logic for leaving team 2021-05-06 05:22:34 -04:00
Gerzon Z 4bac054105 Minor tweaks for refactored touch component 2021-05-05 08:20:48 -04:00
Gerzon Z 7d2924e2e9 Added missing events and fixed channels list 2021-05-05 05:06:45 -04:00
Gerzon Z 4761e21de8 Minor tweaks for removing channels and addExistingChannelView 2021-05-05 03:46:18 -04:00
Gerzon Z 69ae247329 Minor tweaks 2021-05-05 02:37:57 -04:00
Gerzon Z c3ffa37323 Refactor touch component and update removeRoom and deleteRoom methods 2021-05-04 14:02:01 -04:00
Gerzon Z bb0632b689 Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView 2021-04-30 13:09:10 -04:00
Gerzon Z 37421d395a Merge branch 'develop' into new.add-remove-channel-teams 2021-04-28 23:08:15 -04:00
Gerzon Z 0cf173d7ba Added AddChannelTeamView 2021-04-26 14:06:22 -04:00
Gerzon Z 69214bee93 Minor tweak 2021-04-26 14:03:05 -04:00
Gerzon Z 1ff77118f5 Show TeamChannelsView only if joined the team 2021-04-26 10:21:04 -04:00
Gerzon Z 16fd57527a Minor tweaks 2021-04-26 10:08:24 -04:00
Gerzon Z a245af6c1c Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view 2021-04-23 08:28:01 -04:00
Gerzon Z c8b8680541 Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView 2021-04-20 19:07:33 -04:00
Gerzon Z 8b82bd456e Added actionTypes, actions, ENG strings for Teams and updated NewMessageView 2021-04-20 05:30:30 -04:00
Gerzon Z 585a9aea9a
Merge branch 'develop' into new.create-team 2021-04-19 18:59:59 -04:00
Gerzon Z c8a88c9b02
Merge branch 'develop' into new.create-team 2021-04-19 15:25:01 -04:00
Gerzon Z 2f5f247915 Added Create Team 2021-04-19 15:21:27 -04:00
37 changed files with 10315 additions and 7776 deletions

File diff suppressed because it is too large Load Diff

View File

@ -83,8 +83,7 @@ const Button = React.memo(({
}) => ( }) => (
<Touch <Touch
onPress={() => onPress(props.title)} onPress={() => onPress(props.title)}
style={{ backgroundColor: backgroundColor || themes[props.theme].backgroundColor }} style={({ pressed }) => [{ backgroundColor: pressed ? underlayColor || themes[props.theme].bannerBackground : backgroundColor || themes[props.theme].backgroundColor }]}
underlayColor={underlayColor}
enabled={!props.disabled} enabled={!props.disabled}
theme={props.theme} theme={props.theme}
> >

View File

@ -7,6 +7,7 @@ import { connect } from 'react-redux';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import * as AppleAuthentication from 'expo-apple-authentication'; import * as AppleAuthentication from 'expo-apple-authentication';
import { transparentize } from 'color2k';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
@ -344,10 +345,8 @@ class LoginServices extends React.PureComponent {
<Touch <Touch
key={service.name} key={service.name}
onPress={onPress} onPress={onPress}
style={[styles.serviceButton, { backgroundColor }]} style={({ pressed }) => [styles.serviceButton, { backgroundColor: pressed ? transparentize(themes[theme].buttonText, 0.5) : backgroundColor }]}
theme={theme} theme={theme}
activeOpacity={0.5}
underlayColor={themes[theme].buttonText}
> >
<View style={styles.serviceButtonContainer}> <View style={styles.serviceButtonContainer}>
{service.authType === 'oauth' || service.authType === 'apple' ? <CustomIcon name={icon} size={24} color={themes[theme].titleText} style={styles.serviceIcon} /> : null} {service.authType === 'oauth' || service.authType === 'apple' ? <CustomIcon name={icon} size={24} color={themes[theme].titleText} style={styles.serviceIcon} /> : null}

View File

@ -14,7 +14,7 @@ const Button = React.memo(({
return ( return (
<Touch <Touch
style={[styles.buttonView, { backgroundColor: 'transparent' }]} style={({ pressed }) => [styles.buttonView, { backgroundColor: pressed ? 'transparent' : themes[theme].bannerBackground }]}
underlayColor={themes[theme].passcodeButtonActive} underlayColor={themes[theme].passcodeButtonActive}
rippleColor={themes[theme].passcodeButtonActive} rippleColor={themes[theme].passcodeButtonActive}
enabled={!disabled} enabled={!disabled}

View File

@ -290,6 +290,7 @@
"last_message": "last message", "last_message": "last message",
"Leave_channel": "Leave channel", "Leave_channel": "Leave channel",
"leaving_room": "leaving room", "leaving_room": "leaving room",
"Leave": "Leave",
"leave": "leave", "leave": "leave",
"Legal": "Legal", "Legal": "Legal",
"Light": "Light", "Light": "Light",
@ -435,6 +436,7 @@
"Review_app_unable_store": "Unable to open {{store}}", "Review_app_unable_store": "Unable to open {{store}}",
"Review_this_app": "Review this app", "Review_this_app": "Review this app",
"Remove": "Remove", "Remove": "Remove",
"remove": "remove",
"Roles": "Roles", "Roles": "Roles",
"Room_actions": "Room actions", "Room_actions": "Room actions",
"Room_changed_announcement": "Room announcement changed to: {{announcement}} by {{userBy}}", "Room_changed_announcement": "Room announcement changed to: {{announcement}} by {{userBy}}",
@ -709,5 +711,33 @@
"This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}", "This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}",
"Teams": "Teams", "Teams": "Teams",
"No_team_channels_found": "No channels found", "No_team_channels_found": "No channels found",
"Team_not_found": "Team not found" "Team_not_found": "Team not found",
"Create_Team": "Create Team",
"Team_Name": "Team Name",
"Private_Team": "Private Team",
"Read_Only_Team": "Read Only Team",
"Broadcast_Team": "Broadcast Team",
"creating_team" : "creating team",
"team-name-already-exists": "A team with that name already exists",
"Add_Channel_to_Team": "Add Channel to Team",
"Create_New": "Create New",
"Add_Existing": "Add Existing",
"Add_Existing_Channel": "Add Existing Channel",
"Remove_from_Team": "Remove from Team",
"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",
"You_are_leaving_the_team": "You are leaving the team '{{team}}'",
"Leave_Team": "Leave Team",
"Select_Teams": "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",
"Removing_user_from_this_Team": "You are removing {{user}} from this Team",
"Remove_User_Teams": "Select channels you want the user to be removed from.",
"Remove_Member": "Remove Member",
"Error": "Error",
"Delete_Team": "Delete Team",
"Delete_Team_Warning": "You are deleting this team."
} }

View File

@ -13,6 +13,8 @@ const PERMISSIONS = [
'add-user-to-any-c-room', 'add-user-to-any-c-room',
'add-user-to-any-p-room', 'add-user-to-any-p-room',
'add-user-to-joined-room', 'add-user-to-joined-room',
'add-team-member',
'add-team-channel',
'archive-room', 'archive-room',
'auto-translate', 'auto-translate',
'create-invite-links', 'create-invite-links',
@ -21,11 +23,14 @@ const PERMISSIONS = [
'delete-p', 'delete-p',
'edit-message', 'edit-message',
'edit-room', 'edit-room',
'edit-team-member',
'edit-team-channel',
'force-delete-message', 'force-delete-message',
'mute-user', 'mute-user',
'pin-message', 'pin-message',
'post-readonly', 'post-readonly',
'remove-user', 'remove-user',
'remove-team-channel',
'set-leader', 'set-leader',
'set-moderator', 'set-moderator',
'set-owner', 'set-owner',
@ -38,7 +43,9 @@ const PERMISSIONS = [
'view-privileged-setting', 'view-privileged-setting',
'view-room-administration', 'view-room-administration',
'view-statistics', 'view-statistics',
'view-user-administration' 'view-user-administration',
'view-all-teams',
'view-all-team-channels'
]; ];
export async function setPermissions() { export async function setPermissions() {

View File

@ -1,13 +1,20 @@
import log from '../../utils/log'; import log from '../../utils/log';
import updateMessages from './updateMessages'; import updateMessages from './updateMessages';
async function load({ rid: roomId, latest, t }) { async function load({
let params = { roomId, count: 50 }; rid: roomId, latest, t, team
}) {
let params = { roomId: roomId || team.roomId, count: 50 };
let apiType;
if (latest) { if (latest) {
params = { ...params, latest: new Date(latest).toISOString() }; params = { ...params, latest: new Date(latest).toISOString() };
} }
const apiType = this.roomTypeToApiType(t); if (team.type) {
apiType = this.roomTypeToApiType('p');
} else {
apiType = this.roomTypeToApiType(t || 'c');
}
if (!apiType) { if (!apiType) {
return []; return [];
} }

View File

@ -728,7 +728,54 @@ const RocketChat = {
prid, pmid, t_name, reply, users, encrypted prid, pmid, t_name, reply, users, encrypted
}); });
}, },
createTeam({
name, users, type, readOnly, broadcast, encrypted
}) {
const params = {
name,
users,
type,
room: {
readOnly,
extraData: {
broadcast,
encrypted
}
}
};
// RC 3.13.0
return this.post('teams.create', params);
},
addTeamRooms({ rooms, teamId }) {
const params = {
rooms: Array.isArray(rooms) ? rooms : [rooms],
teamId
};
// RC 3.13.0
return this.post('teams.addRooms', params);
},
removeTeamRoom({ roomId, teamId }) {
// RC 3.13.0
return this.post('teams.removeRoom', { roomId, teamId });
},
leaveTeam({ teamName }) {
// RC 3.13.0
return this.post('teams.leave', { teamName });
},
addTeamMember(teamName) {
const { users } = reduxStore.getState().selectedUsers;
const members = users.map(u => ({ userId: u._id, roles: ['member'] }));
// RC 3.13.0
return this.post('teams.addMembers', { teamName, members });
},
removeTeamMember({ teamName, userId }) {
// RC 3.13.0
return this.post('teams.removeMember', { teamName, userId });
},
deleteTeam({ teamName }) {
// RC 3.13.0
return this.post('teams.delete', { teamName });
},
joinRoom(roomId, joinCode, type) { joinRoom(roomId, joinCode, type) {
// TODO: join code // TODO: join code
// RC 0.48.0 // RC 0.48.0

View File

@ -24,7 +24,6 @@ const RoomItem = ({
status, status,
useRealName, useRealName,
theme, theme,
isFocused,
isGroupChat, isGroupChat,
isRead, isRead,
date, date,
@ -42,6 +41,7 @@ const RoomItem = ({
testID, testID,
swipeEnabled, swipeEnabled,
onPress, onPress,
onLongPress,
toggleFav, toggleFav,
toggleRead, toggleRead,
hideChannel, hideChannel,
@ -49,6 +49,7 @@ const RoomItem = ({
}) => ( }) => (
<Touchable <Touchable
onPress={onPress} onPress={onPress}
onLongPress={onLongPress}
width={width} width={width}
favorite={favorite} favorite={favorite}
toggleFav={toggleFav} toggleFav={toggleFav}
@ -59,7 +60,6 @@ const RoomItem = ({
testID={testID} testID={testID}
type={type} type={type}
theme={theme} theme={theme}
isFocused={isFocused}
swipeEnabled={swipeEnabled} swipeEnabled={swipeEnabled}
> >
<Wrapper <Wrapper
@ -161,7 +161,6 @@ RoomItem.propTypes = {
status: PropTypes.string, status: PropTypes.string,
useRealName: PropTypes.bool, useRealName: PropTypes.bool,
theme: PropTypes.string, theme: PropTypes.string,
isFocused: PropTypes.bool,
isGroupChat: PropTypes.bool, isGroupChat: PropTypes.bool,
isRead: PropTypes.bool, isRead: PropTypes.bool,
teamMain: PropTypes.bool, teamMain: PropTypes.bool,
@ -181,6 +180,7 @@ RoomItem.propTypes = {
toggleFav: PropTypes.func, toggleFav: PropTypes.func,
toggleRead: PropTypes.func, toggleRead: PropTypes.func,
onPress: PropTypes.func, onPress: PropTypes.func,
onLongPress: PropTypes.func,
hideChannel: PropTypes.func hideChannel: PropTypes.func
}; };

View File

@ -17,6 +17,7 @@ class Touchable extends React.Component {
static propTypes = { static propTypes = {
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
onPress: PropTypes.func, onPress: PropTypes.func,
onLongPress: PropTypes.func,
testID: PropTypes.string, testID: PropTypes.string,
width: PropTypes.number, width: PropTypes.number,
favorite: PropTypes.bool, favorite: PropTypes.bool,
@ -27,7 +28,6 @@ class Touchable extends React.Component {
hideChannel: PropTypes.func, hideChannel: PropTypes.func,
children: PropTypes.element, children: PropTypes.element,
theme: PropTypes.string, theme: PropTypes.string,
isFocused: PropTypes.bool,
swipeEnabled: PropTypes.bool swipeEnabled: PropTypes.bool
} }
@ -203,9 +203,21 @@ class Touchable extends React.Component {
} }
}; };
onLongPress = () => {
const { rowState } = this.state;
if (rowState !== 0) {
this.close();
return;
}
const { onLongPress } = this.props;
if (onLongPress) {
onLongPress();
}
};
render() { render() {
const { const {
testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled testID, isRead, width, favorite, children, theme, swipeEnabled
} = this.props; } = this.props;
return ( return (
@ -239,11 +251,10 @@ class Touchable extends React.Component {
> >
<Touch <Touch
onPress={this.onPress} onPress={this.onPress}
onLongPress={this.onLongPress}
theme={theme} theme={theme}
testID={testID} testID={testID}
style={{ style={({ pressed }) => [{ backgroundColor: pressed ? themes[theme].chatComponentBackground : themes[theme].backgroundColor }]}
backgroundColor: isFocused ? themes[theme].chatComponentBackground : themes[theme].backgroundColor
}}
> >
{children} {children}
</Touch> </Touch>

View File

@ -14,7 +14,6 @@ const attrs = [
'status', 'status',
'connected', 'connected',
'theme', 'theme',
'isFocused',
'forceUpdate', 'forceUpdate',
'showLastMessage' 'showLastMessage'
]; ];
@ -25,6 +24,7 @@ class RoomItemContainer extends React.Component {
showLastMessage: PropTypes.bool, showLastMessage: PropTypes.bool,
id: PropTypes.string, id: PropTypes.string,
onPress: PropTypes.func, onPress: PropTypes.func,
onLongPress: PropTypes.func,
username: PropTypes.string, username: PropTypes.string,
avatarSize: PropTypes.number, avatarSize: PropTypes.number,
width: PropTypes.number, width: PropTypes.number,
@ -36,7 +36,6 @@ class RoomItemContainer extends React.Component {
getUserPresence: PropTypes.func, getUserPresence: PropTypes.func,
connected: PropTypes.bool, connected: PropTypes.bool,
theme: PropTypes.string, theme: PropTypes.string,
isFocused: PropTypes.bool,
getRoomTitle: PropTypes.func, getRoomTitle: PropTypes.func,
getRoomAvatar: PropTypes.func, getRoomAvatar: PropTypes.func,
getIsGroupChat: PropTypes.func, getIsGroupChat: PropTypes.func,
@ -112,6 +111,11 @@ class RoomItemContainer extends React.Component {
return onPress(item); return onPress(item);
} }
onLongPress = () => {
const { item, onLongPress } = this.props;
return onLongPress(item);
}
render() { render() {
const { const {
item, item,
@ -123,7 +127,6 @@ class RoomItemContainer extends React.Component {
toggleRead, toggleRead,
hideChannel, hideChannel,
theme, theme,
isFocused,
avatarSize, avatarSize,
status, status,
showLastMessage, showLastMessage,
@ -160,6 +163,7 @@ class RoomItemContainer extends React.Component {
isGroupChat={this.isGroupChat} isGroupChat={this.isGroupChat}
isRead={isRead} isRead={isRead}
onPress={this.onPress} onPress={this.onPress}
onLongPress={this.onLongPress}
date={date} date={date}
accessibilityLabel={accessibilityLabel} accessibilityLabel={accessibilityLabel}
width={width} width={width}
@ -171,7 +175,6 @@ class RoomItemContainer extends React.Component {
testID={testID} testID={testID}
type={item.t} type={item.t}
theme={theme} theme={theme}
isFocused={isFocused}
size={avatarSize} size={avatarSize}
prid={item.prid} prid={item.prid}
status={status} status={status}

View File

@ -21,6 +21,14 @@ const createGroupChat = function createGroupChat() {
return RocketChat.createGroupChat(); return RocketChat.createGroupChat();
}; };
const createTeam = function createTeam(data) {
return RocketChat.createTeam(data);
};
const addTeamRoom = function addRoomToTeam(params) {
return RocketChat.addTeamRooms(params);
};
const handleRequest = function* handleRequest({ data }) { const handleRequest = function* handleRequest({ data }) {
try { try {
const auth = yield select(state => state.login.isAuthenticated); const auth = yield select(state => state.login.isAuthenticated);
@ -29,7 +37,21 @@ const handleRequest = function* handleRequest({ data }) {
} }
let sub; let sub;
if (data.group) { if (data.isTeam) {
const {
type,
readOnly,
broadcast,
encrypted
} = data;
logEvent(events.CT_CREATE, {
type,
readOnly,
broadcast,
encrypted
});
sub = yield call(createTeam, data);
} else if (data.group) {
logEvent(events.SELECTED_USERS_CREATE_GROUP); logEvent(events.SELECTED_USERS_CREATE_GROUP);
const result = yield call(createGroupChat); const result = yield call(createGroupChat);
if (result.success) { if (result.success) {
@ -49,14 +71,22 @@ const handleRequest = function* handleRequest({ data }) {
encrypted encrypted
}); });
sub = yield call(createChannel, data); sub = yield call(createChannel, data);
}
if (data.teamId) {
logEvent(events.CT_ADD_ROOM_TO_TEAM);
const channels = yield call(addTeamRoom, { rooms: sub.rid, teamId: data.teamId });
if (channels.success) {
sub.teamId = channels.rooms[0].teamId;
sub.isTeamChannel = true;
}
}
}
try { try {
const db = database.active; const db = database.active;
const subCollection = db.get('subscriptions'); const subCollection = db.get('subscriptions');
yield db.action(async() => { yield db.action(async() => {
await subCollection.create((s) => { await subCollection.create((s) => {
s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); s._raw = sanitizedRaw({ id: sub.team ? sub.team.roomId : sub.rid, team_id: sub.teamId }, subCollection.schema);
Object.assign(s, sub); Object.assign(s, sub);
}); });
}); });
@ -76,12 +106,12 @@ const handleSuccess = function* handleSuccess({ data }) {
if (isMasterDetail) { if (isMasterDetail) {
Navigation.navigate('DrawerNavigator'); Navigation.navigate('DrawerNavigator');
} }
goRoom({ item: data, isMasterDetail }); goRoom({ item: data.team ? data.team : data, isMasterDetail });
}; };
const handleFailure = function handleFailure({ err }) { const handleFailure = function handleFailure({ err }) {
setTimeout(() => { setTimeout(() => {
const msg = err.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') }); const msg = err.data ? I18n.t(err.data.error) : err.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
showErrorAlert(msg); showErrorAlert(msg);
}, 300); }, 300);
}; };

View File

@ -71,6 +71,9 @@ import ShareView from '../views/ShareView';
import CreateDiscussionView from '../views/CreateDiscussionView'; import CreateDiscussionView from '../views/CreateDiscussionView';
import QueueListView from '../ee/omnichannel/views/QueueListView'; import QueueListView from '../ee/omnichannel/views/QueueListView';
import AddChannelTeamView from '../views/AddChannelTeamView';
import AddExistingChannelView from '../views/AddExistingChannelView';
import SelectListView from '../views/SelectListView';
// ChatsStackNavigator // ChatsStackNavigator
const ChatsStack = createStackNavigator(); const ChatsStack = createStackNavigator();
@ -91,6 +94,11 @@ const ChatsStackNavigator = () => {
component={RoomActionsView} component={RoomActionsView}
options={RoomActionsView.navigationOptions} options={RoomActionsView.navigationOptions}
/> />
<ChatsStack.Screen
name='SelectListView'
component={SelectListView}
options={SelectListView.navigationOptions}
/>
<ChatsStack.Screen <ChatsStack.Screen
name='RoomInfoView' name='RoomInfoView'
component={RoomInfoView} component={RoomInfoView}
@ -174,6 +182,16 @@ const ChatsStackNavigator = () => {
component={TeamChannelsView} component={TeamChannelsView}
options={TeamChannelsView.navigationOptions} options={TeamChannelsView.navigationOptions}
/> />
<ChatsStack.Screen
name='AddChannelTeamView'
component={AddChannelTeamView}
options={AddChannelTeamView.navigationOptions}
/>
<ChatsStack.Screen
name='AddExistingChannelView'
component={AddExistingChannelView}
options={AddExistingChannelView.navigationOptions}
/>
<ChatsStack.Screen <ChatsStack.Screen
name='MarkdownTableView' name='MarkdownTableView'
component={MarkdownTableView} component={MarkdownTableView}

View File

@ -8,17 +8,36 @@ const navigate = ({ item, isMasterDetail, ...props }) => {
navigationMethod = Navigation.replace; navigationMethod = Navigation.replace;
} }
navigationMethod('RoomView', { if (item.isTeamChannel) {
rid: item.rid, // TODO: Refactor
name: RocketChat.getRoomTitle(item), Navigation.navigate('TeamChannelsView');
t: item.t, Navigation.push('RoomView', {
prid: item.prid, rid: item.roomId || item.rid,
room: item, name: RocketChat.getRoomTitle(item),
search: item.search, t: item.type ? 'p' : item.t,
visitor: item.visitor, prid: item.prid,
roomUserId: RocketChat.getUidDirectMessage(item), room: item,
...props search: item.search,
}); visitor: item.visitor,
roomUserId: RocketChat.getUidDirectMessage(item),
teamId: item.teamId,
...props
});
} else if (item.rooms) {
Navigation.navigate('TeamChannelsView');
} else {
navigationMethod('RoomView', {
rid: item.roomId || item.rid,
name: RocketChat.getRoomTitle(item),
t: item.type ? 'p' : item.t,
prid: item.prid,
room: item,
search: item.search,
visitor: item.visitor,
roomUserId: RocketChat.getUidDirectMessage(item),
...props
});
}
}; };
export const goRoom = async({ item = {}, isMasterDetail = false, ...props }) => { export const goRoom = async({ item = {}, isMasterDetail = false, ...props }) => {

View File

@ -88,6 +88,7 @@ export default {
// NEW MESSAGE VIEW // NEW MESSAGE VIEW
NEW_MSG_CREATE_CHANNEL: 'new_msg_create_channel', NEW_MSG_CREATE_CHANNEL: 'new_msg_create_channel',
NEW_MSG_CREATE_TEAM: 'new_msg_create_team',
NEW_MSG_CREATE_GROUP_CHAT: 'new_msg_create_group_chat', NEW_MSG_CREATE_GROUP_CHAT: 'new_msg_create_group_chat',
NEW_MSG_CREATE_DISCUSSION: 'new_msg_create_discussion', NEW_MSG_CREATE_DISCUSSION: 'new_msg_create_discussion',
NEW_MSG_CHAT_WITH_USER: 'new_msg_chat_with_user', NEW_MSG_CHAT_WITH_USER: 'new_msg_chat_with_user',
@ -100,12 +101,16 @@ export default {
// CREATE CHANNEL VIEW // CREATE CHANNEL VIEW
CR_CREATE: 'cr_create', CR_CREATE: 'cr_create',
CT_CREATE: 'ct_create',
CR_CREATE_F: 'cr_create_f', CR_CREATE_F: 'cr_create_f',
CT_CREATE_F: 'ct_create_f',
CR_TOGGLE_TYPE: 'cr_toggle_type', CR_TOGGLE_TYPE: 'cr_toggle_type',
CR_TOGGLE_READ_ONLY: 'cr_toggle_read_only', CR_TOGGLE_READ_ONLY: 'cr_toggle_read_only',
CR_TOGGLE_BROADCAST: 'cr_toggle_broadcast', CR_TOGGLE_BROADCAST: 'cr_toggle_broadcast',
CR_TOGGLE_ENCRYPTED: 'cr_toggle_encrypted', CR_TOGGLE_ENCRYPTED: 'cr_toggle_encrypted',
CR_REMOVE_USER: 'cr_remove_user', CR_REMOVE_USER: 'cr_remove_user',
CT_ADD_ROOM_TO_TEAM: 'ct_add_room_to_team',
CT_ADD_ROOM_TO_TEAM_F: 'ct_add_room_to_team_f',
// CREATE DISCUSSION VIEW // CREATE DISCUSSION VIEW
CD_CREATE: 'cd_create', CD_CREATE: 'cd_create',

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { RectButton } from 'react-native-gesture-handler';
import { Pressable } from 'react-native';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
class Touch extends React.Component { class Touch extends React.Component {
@ -15,20 +15,21 @@ class Touch extends React.Component {
render() { render() {
const { const {
children, onPress, theme, underlayColor, ...props children, onPress, onLongPress, theme, style, ...props
} = this.props; } = this.props;
return ( return (
<RectButton <Pressable
ref={this.getRef} ref={this.getRef}
onPress={onPress} onPress={onPress}
onLongPress={onLongPress}
activeOpacity={1} activeOpacity={1}
underlayColor={underlayColor || themes[theme].bannerBackground} style={style}
rippleColor={themes[theme].bannerBackground} android_ripple={{ color: themes[theme].bannerBackground }}
{...props} {...props}
> >
{children} {children}
</RectButton> </Pressable>
); );
} }
} }
@ -36,8 +37,10 @@ class Touch extends React.Component {
Touch.propTypes = { Touch.propTypes = {
children: PropTypes.node, children: PropTypes.node,
onPress: PropTypes.func, onPress: PropTypes.func,
onLongPress: PropTypes.func,
theme: PropTypes.string, theme: PropTypes.string,
underlayColor: PropTypes.string underlayColor: PropTypes.string,
style: PropTypes.object
}; };
export default Touch; export default Touch;

View File

@ -0,0 +1,123 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { HeaderBackButton } from '@react-navigation/stack';
import sharedStyles from './Styles';
import { CustomIcon } from '../lib/Icons';
import Touch from '../utils/touch';
import StatusBar from '../containers/StatusBar';
import { withTheme } from '../theme';
import * as HeaderButton from '../containers/HeaderButton';
import SafeAreaView from '../containers/SafeAreaView';
import { withDimensions } from '../dimensions';
import { themes } from '../constants/colors';
import I18n from '../i18n';
const styles = StyleSheet.create({
button: {
height: 46,
flexDirection: 'row',
alignItems: 'center'
},
buttonIcon: {
marginLeft: 18,
marginRight: 16
},
buttonText: {
fontSize: 17,
...sharedStyles.textRegular
},
buttonContainer: {
paddingVertical: 25
}
});
class AddChannelTeamView extends React.Component {
constructor(props) {
super(props);
this.teamId = props.route.params?.teamId;
this.setHeader();
}
setHeader = () => {
const { navigation, isMasterDetail, theme } = this.props;
const options = {
headerShown: true,
headerTitleAlign: 'center',
headerTitle: I18n.t('Add_Channel_to_Team')
};
if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
} else {
options.headerLeft = () => (
<HeaderBackButton
labelVisible={false}
onPress={() => navigation.pop()}
tintColor={themes[theme].headerTintColor}
/>
);
}
navigation.setOptions(options);
}
renderButton = ({
onPress, testID, title, icon, first
}) => {
const { theme } = this.props;
return (
<Touch
onPress={onPress}
style={({ pressed }) => [{
backgroundColor: pressed ? themes[theme].chatComponentBackground : themes[theme].backgroundColor
}]}
testID={testID}
theme={theme}
>
<View style={[first ? sharedStyles.separatorVertical : sharedStyles.separatorBottom, styles.button, { borderColor: themes[theme].separatorColor }]}>
<CustomIcon style={[styles.buttonIcon, { color: themes[theme].tintColor }]} size={24} name={icon} />
<Text style={[styles.buttonText, { color: themes[theme].tintColor }]}>{title}</Text>
</View>
</Touch>
);
}
render() {
const { navigation, route } = this.props;
const { teamChannels } = route?.params;
return (
<SafeAreaView testID='add-channel-team-view'>
<StatusBar />
<View style={styles.buttonContainer}>
{this.renderButton({
onPress: () => navigation.navigate('NewMessageStackNavigator', { screen: 'SelectedUsersViewCreateChannel', params: { nextAction: () => navigation.navigate('CreateChannelView', { teamId: this.teamId }) } }),
title: I18n.t('Create_New'),
icon: 'channel-public',
testID: 'add-channel-team-view-create-channel',
first: true
})}
{this.renderButton({
onPress: () => navigation.navigate('AddExistingChannelView', { teamId: this.teamId, teamChannels }),
title: I18n.t('Add_Existing'),
icon: 'team',
testID: 'add-channel-team-view-create-channel'
})}
</View>
</SafeAreaView>
);
}
}
AddChannelTeamView.propTypes = {
route: PropTypes.object,
navigation: PropTypes.object,
isMasterDetail: PropTypes.bool,
theme: PropTypes.string
};
export default withDimensions(withTheme(AddChannelTeamView));

View File

@ -0,0 +1,265 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import React from 'react';
import PropTypes from 'prop-types';
import {
View, StyleSheet, FlatList, Text
} from 'react-native';
import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb';
import { HeaderBackButton } from '@react-navigation/stack';
import * as List from '../containers/List';
import Touch from '../utils/touch';
import database from '../lib/database';
import RocketChat from '../lib/rocketchat';
import sharedStyles from './Styles';
import I18n from '../i18n';
import log, { events, logEvent } from '../utils/log';
import SearchBox from '../containers/SearchBox';
import { CustomIcon } from '../lib/Icons';
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 { goRoom } from '../utils/goRoom';
import Loading from '../containers/Loading';
const QUERY_SIZE = 15;
const styles = StyleSheet.create({
button: {
height: 46,
flexDirection: 'row',
alignItems: 'center'
},
buttonIcon: {
marginLeft: 18,
marginRight: 16
},
buttonText: {
fontSize: 17,
...sharedStyles.textRegular
},
textContainer: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
marginRight: 15
},
icon: {
marginHorizontal: 15,
alignSelf: 'center'
}
});
class AddExistingChannelView extends React.Component {
static propTypes = {
navigation: PropTypes.object,
route: PropTypes.object,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
}),
theme: PropTypes.string,
isMasterDetail: PropTypes.bool,
addTeamChannelPermission: PropTypes.array
};
constructor(props) {
super(props);
this.init();
this.teamId = props.route?.params?.teamId;
this.state = {
search: [],
channels: [],
selected: [],
loading: false
};
this.setHeader();
}
setHeader = () => {
const { navigation, isMasterDetail, theme } = this.props;
const { selected } = this.state;
const options = {
headerShown: true,
headerTitleAlign: 'center',
headerTitle: I18n.t('Add_Existing_Channel')
};
if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
} else {
options.headerLeft = () => <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />;
}
options.headerRight = () => selected.length > 0 && (
<HeaderButton.Container>
<HeaderButton.Item title={I18n.t('Create')} onPress={this.submit} testID='add-existing-channel-view-submit' />
</HeaderButton.Container>
);
navigation.setOptions(options);
}
// eslint-disable-next-line react/sort-comp
init = async() => {
try {
const { addTeamChannelPermission } = this.props;
const db = database.active;
const channels = await db.collections
.get('subscriptions')
.query(
Q.and(Q.where('team_id', ''), Q.or(Q.where('t', 'c'), Q.where('t', 'p'))),
Q.experimentalTake(QUERY_SIZE),
Q.experimentalSortBy('room_updated_at', Q.desc)
)
.fetch();
const filteredChannels = channels.filter(async(channel) => {
const permissions = await RocketChat.hasPermission([addTeamChannelPermission], channel.rid);
if (!permissions[0]) {
return;
}
return channel;
});
this.setState({ channels: filteredChannels });
} catch (e) {
log(e);
}
}
onSearchChangeText(text) {
this.search(text);
}
dismiss = () => {
const { navigation } = this.props;
return navigation.pop();
}
search = async(text) => {
const result = await RocketChat.search({ text, filterUsers: false });
this.setState({
search: result
});
}
submit = async() => {
const { selected } = this.state;
const { isMasterDetail } = this.props;
this.setState({ loading: true });
try {
logEvent(events.CT_ADD_ROOM_TO_TEAM);
const result = await RocketChat.addTeamRooms({ rooms: selected, teamId: this.teamId });
if (result.success) {
this.setState({ loading: false });
goRoom({ item: result, isMasterDetail });
}
} catch (e) {
logEvent(events.CT_ADD_ROOM_TO_TEAM_F);
this.setState({ loading: false });
}
}
renderChannel = ({
onPress, testID, title, icon, checked
}) => {
const { theme } = this.props;
return (
<Touch
onPress={onPress}
style={{ backgroundColor: themes[theme].backgroundColor }}
testID={testID}
theme={theme}
>
<View style={[styles.button, { borderColor: themes[theme].separatorColor, marginVertical: 4 }]}>
<CustomIcon style={[styles.buttonIcon, { color: themes[theme].controlText }]} size={24} name={icon} />
<View style={styles.textContainer}>
<Text style={[styles.buttonText, { color: themes[theme].bodyText }]}>{title}</Text>
</View>
{checked ? <CustomIcon name={checked} size={22} style={[styles.icon, { color: themes[theme].actionTintColor }]} /> : null}
</View>
</Touch>
);
}
renderHeader = () => {
const { theme } = this.props;
return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='add-existing-channel-view-search' />
</View>
);
}
isChecked = (rid) => {
const { selected } = this.state;
return selected.includes(rid);
}
toggleChannel = (rid) => {
const { selected } = this.state;
animateNextTransition();
if (!this.isChecked(rid)) {
// logEvent(events.SELECTED_USERS_ADD_USER);
this.setState({ selected: [...selected, rid] }, () => this.setHeader());
} else {
// logEvent(events.SELECTED_USERS_REMOVE_USER);
const filterSelected = selected.filter(el => el !== rid);
this.setState({ selected: filterSelected }, () => this.setHeader());
}
}
renderItem = ({ item }) => (
<>
{this.renderChannel({
onPress: () => this.toggleChannel(item.rid),
title: item.name,
icon: item.t === 'p' && !item.teamId ? 'channel-private' : 'channel-public',
checked: this.isChecked(item.rid) ? 'check' : null,
testID: 'add-existing-channel-view-item'
})}
</>
)
renderList = () => {
const { search, channels } = this.state;
const { theme } = this.props;
return (
<FlatList
data={search.length > 0 ? search : channels}
extraData={this.state}
keyExtractor={item => item._id}
ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always'
/>
);
}
render() {
const { loading } = this.state;
return (
<SafeAreaView testID='new-message-view'>
<StatusBar />
{this.renderList()}
<Loading visible={loading} />
</SafeAreaView>
);
}
}
const mapStateToProps = state => ({
isMasterDetail: state.app.isMasterDetail,
addTeamChannelPermission: state.permissions['add-team-channel']
});
export default connect(mapStateToProps, null)(withTheme(AddExistingChannelView));

View File

@ -69,11 +69,12 @@ const styles = StyleSheet.create({
class CreateChannelView extends React.Component { class CreateChannelView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
title: I18n.t('Create_Channel') title: this.isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')
}); })
static propTypes = { static propTypes = {
navigation: PropTypes.object, navigation: PropTypes.object,
route: PropTypes.object,
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
create: PropTypes.func.isRequired, create: PropTypes.func.isRequired,
removeUser: PropTypes.func.isRequired, removeUser: PropTypes.func.isRequired,
@ -86,15 +87,22 @@ class CreateChannelView extends React.Component {
id: PropTypes.string, id: PropTypes.string,
token: PropTypes.string token: PropTypes.string
}), }),
theme: PropTypes.string theme: PropTypes.string,
teamId: PropTypes.string
}; };
state = { constructor(props) {
channelName: '', super(props);
type: true, const { route } = this.props;
readOnly: false, this.isTeam = route?.params?.isTeam || false;
encrypted: false, this.teamId = route?.params?.teamId;
broadcast: false this.state = {
channelName: '',
type: true,
readOnly: false,
encrypted: false,
broadcast: false
};
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
@ -154,7 +162,9 @@ class CreateChannelView extends React.Component {
const { const {
channelName, type, readOnly, broadcast, encrypted channelName, type, readOnly, broadcast, encrypted
} = this.state; } = this.state;
const { users: usersProps, isFetching, create } = this.props; const {
users: usersProps, isFetching, create
} = this.props;
if (!channelName.trim() || isFetching) { if (!channelName.trim() || isFetching) {
return; return;
@ -163,9 +173,9 @@ class CreateChannelView extends React.Component {
// transform users object into array of usernames // transform users object into array of usernames
const users = usersProps.map(user => user.name); const users = usersProps.map(user => user.name);
// create channel // create channel or team
create({ create({
name: channelName, users, type, readOnly, broadcast, encrypted name: channelName, users, type, readOnly, broadcast, encrypted, isTeam: this.isTeam, teamId: this.teamId
}); });
Review.pushPositiveEvent(); Review.pushPositiveEvent();
@ -197,10 +207,11 @@ class CreateChannelView extends React.Component {
renderType() { renderType() {
const { type } = this.state; const { type } = this.state;
return this.renderSwitch({ return this.renderSwitch({
id: 'type', id: 'type',
value: type, value: type,
label: 'Private_Channel', label: this.isTeam ? 'Private_Team' : 'Private_Channel',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CR_TOGGLE_TYPE); logEvent(events.CR_TOGGLE_TYPE);
// If we set the channel as public, encrypted status should be false // If we set the channel as public, encrypted status should be false
@ -211,10 +222,11 @@ class CreateChannelView extends React.Component {
renderReadOnly() { renderReadOnly() {
const { readOnly, broadcast } = this.state; const { readOnly, broadcast } = this.state;
return this.renderSwitch({ return this.renderSwitch({
id: 'readonly', id: 'readonly',
value: readOnly, value: readOnly,
label: 'Read_Only_Channel', label: this.isTeam ? 'Read_Only_Team' : 'Read_Only_Channel',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CR_TOGGLE_READ_ONLY); logEvent(events.CR_TOGGLE_READ_ONLY);
this.setState({ readOnly: value }); this.setState({ readOnly: value });
@ -245,10 +257,11 @@ class CreateChannelView extends React.Component {
renderBroadcast() { renderBroadcast() {
const { broadcast, readOnly } = this.state; const { broadcast, readOnly } = this.state;
return this.renderSwitch({ return this.renderSwitch({
id: 'broadcast', id: 'broadcast',
value: broadcast, value: broadcast,
label: 'Broadcast_Channel', label: this.isTeam ? 'Broadcast_Team' : 'Broadcast_Channel',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CR_TOGGLE_BROADCAST); logEvent(events.CR_TOGGLE_BROADCAST);
this.setState({ this.setState({
@ -302,7 +315,9 @@ class CreateChannelView extends React.Component {
render() { render() {
const { channelName } = this.state; const { channelName } = this.state;
const { users, isFetching, theme } = this.props; const {
users, isFetching, theme
} = this.props;
const userCount = users.length; const userCount = users.length;
return ( return (
@ -312,18 +327,18 @@ class CreateChannelView extends React.Component {
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<StatusBar /> <StatusBar />
<SafeAreaView testID='create-channel-view'> <SafeAreaView testID={this.isTeam ? 'create-team-view' : 'create-channel-view'}>
<ScrollView {...scrollPersistTaps}> <ScrollView {...scrollPersistTaps}>
<View style={[sharedStyles.separatorVertical, { borderColor: themes[theme].separatorColor }]}> <View style={[sharedStyles.separatorVertical, { borderColor: themes[theme].separatorColor }]}>
<TextInput <TextInput
autoFocus autoFocus
style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]} style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]}
label={I18n.t('Channel_Name')} label={this.isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
value={channelName} value={channelName}
onChangeText={this.onChangeText} onChangeText={this.onChangeText}
placeholder={I18n.t('Channel_Name')} placeholder={this.isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
returnKeyType='done' returnKeyType='done'
testID='create-channel-name' testID={this.isTeam ? 'create-team-name' : 'create-channel-name'}
autoCorrect={false} autoCorrect={false}
autoCapitalize='none' autoCapitalize='none'
theme={theme} theme={theme}

View File

@ -67,7 +67,7 @@ export default class DirectoryOptions extends PureComponent {
return ( return (
<Touch <Touch
onPress={() => changeType(itemType)} onPress={() => changeType(itemType)}
style={styles.dropdownItemButton} style={({ pressed }) => [{ backgroundColor: pressed ? themes[theme].bannerBackground : themes[theme].backgroundColor }, styles.dropdownItemButton]}
theme={theme} theme={theme}
> >
<View style={styles.dropdownItemContainer}> <View style={styles.dropdownItemContainer}>

View File

@ -169,7 +169,7 @@ class DirectoryView extends React.Component {
/> />
<Touch <Touch
onPress={this.toggleDropdown} onPress={this.toggleDropdown}
style={styles.dropdownItemButton} style={({ pressed }) => [{ backgroundColor: pressed ? themes[theme].bannerBackground : themes[theme].backgroundColor }, styles.dropdownItemButton]}
testID='directory-view-dropdown' testID='directory-view-dropdown'
theme={theme} theme={theme}
> >

View File

@ -116,6 +116,12 @@ class NewMessageView extends React.Component {
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') }); navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') });
} }
createTeam = () => {
logEvent(events.NEW_MSG_CREATE_TEAM);
const { navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true }) });
}
createGroupChat = () => { createGroupChat = () => {
logEvent(events.NEW_MSG_CREATE_GROUP_CHAT); logEvent(events.NEW_MSG_CREATE_GROUP_CHAT);
const { createChannel, maxUsers, navigation } = this.props; const { createChannel, maxUsers, navigation } = this.props;
@ -142,7 +148,9 @@ class NewMessageView extends React.Component {
return ( return (
<Touch <Touch
onPress={onPress} onPress={onPress}
style={{ backgroundColor: themes[theme].backgroundColor }} style={({ pressed }) => [{
backgroundColor: pressed ? themes[theme].bannerBackground : themes[theme].backgroundColor
}]}
testID={testID} testID={testID}
theme={theme} theme={theme}
> >
@ -172,6 +180,12 @@ class NewMessageView extends React.Component {
testID: 'new-message-view-create-channel', testID: 'new-message-view-create-channel',
first: true first: true
})} })}
{this.renderButton({
onPress: this.createTeam,
title: I18n.t('Create_Team'),
icon: 'team',
testID: 'new-message-view-create-team'
})}
{maxUsers > 2 ? this.renderButton({ {maxUsers > 2 ? this.renderButton({
onPress: this.createGroupChat, onPress: this.createGroupChat,
title: I18n.t('Create_Direct_Messages'), title: I18n.t('Create_Direct_Messages'),
@ -253,7 +267,7 @@ const mapStateToProps = state => ({
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
createChannel: params => dispatch(createChannelRequest(params)) create: params => dispatch(createChannelRequest(params))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewMessageView)); export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewMessageView));

View File

@ -309,7 +309,7 @@ class ProfileView extends React.Component {
key={key} key={key}
testID={key} testID={key}
onPress={onPress} onPress={onPress}
style={[styles.avatarButton, { opacity: disabled ? 0.5 : 1 }, { backgroundColor: themes[theme].borderColor }]} style={({ pressed }) => [styles.avatarButton, { opacity: disabled ? 0.5 : 1 }, { backgroundColor: pressed ? themes[theme].borderColor : themes[theme].bannerBackground }]}
enabled={!disabled} enabled={!disabled}
theme={theme} theme={theme}
> >

View File

@ -5,6 +5,7 @@ import {
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { Q } from '@nozbe/watermelondb';
import { compareServerVersion, methods } from '../../lib/utils'; import { compareServerVersion, methods } from '../../lib/utils';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
@ -60,7 +61,8 @@ class RoomActionsView extends React.Component {
editRoomPermission: PropTypes.array, editRoomPermission: PropTypes.array,
toggleRoomE2EEncryptionPermission: PropTypes.array, toggleRoomE2EEncryptionPermission: PropTypes.array,
viewBroadcastMemberListPermission: PropTypes.array, viewBroadcastMemberListPermission: PropTypes.array,
transferLivechatGuestPermission: PropTypes.array transferLivechatGuestPermission: PropTypes.array,
addTeamMemberPermission: PropTypes.array
} }
constructor(props) { constructor(props) {
@ -170,12 +172,19 @@ class RoomActionsView extends React.Component {
canAddUser = async() => { canAddUser = async() => {
const { room, joined } = this.state; const { room, joined } = this.state;
const { addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission } = this.props; const {
addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission, addTeamMemberPermission
} = this.props;
const { rid, t } = room; const { rid, t } = room;
let canAddUser = false; let canAddUser = false;
let permissions;
const userInRoom = joined; const userInRoom = joined;
const permissions = await RocketChat.hasPermission([addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission], rid); if (room.teamMain) {
permissions = await RocketChat.hasPermission([addTeamMemberPermission], rid);
} else {
permissions = await RocketChat.hasPermission([addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission], rid);
}
if (userInRoom && permissions[0]) { if (userInRoom && permissions[0]) {
canAddUser = true; canAddUser = true;
@ -320,6 +329,31 @@ class RoomActionsView extends React.Component {
setLoadingInvite(true); setLoadingInvite(true);
await RocketChat.addUsersToRoom(rid); await RocketChat.addUsersToRoom(rid);
navigation.pop(); navigation.pop();
} catch (e) {
log(e);
Alert.alert(
I18n.t('Confirmation'),
I18n.t('Removing_user_from_this_Team'),
[
{
text: I18n.t('OK'),
style: 'cancel'
}
],
{ cancelable: false }
);
} finally {
setLoadingInvite(false);
}
}
addMemberToTeam = async() => {
const { room } = this.state;
const { setLoadingInvite, navigation } = this.props;
try {
setLoadingInvite(true);
await RocketChat.addTeamMember(room.name);
navigation.pop();
} catch (e) { } catch (e) {
log(e); log(e);
} finally { } finally {
@ -412,6 +446,56 @@ class RoomActionsView extends React.Component {
); );
} }
_leave = async(teamName) => {
try {
const { navigation } = this.props;
const result = await RocketChat.leaveTeam({ teamName });
// Add isMasterDetail
if (result.success) {
navigation.navigate('RoomsListView');
}
} catch (e) {
log(e);
}
}
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', Q.eq(room.teamId))
);
if (teamChannels) {
navigation.navigate('SelectListView', {
title: 'Leave_Team', room, teamChannels, teamName: room.name, subtitle: 'Select_Teams'
});
} else {
Alert.alert(
I18n.t('Confirmation'),
I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
[
{
text: I18n.t('Cancel'),
style: 'cancel'
},
{
text: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
style: 'destructive',
onPress: () => this._leave(room.name)
}
]
);
}
} catch (e) {
log(e);
}
}
renderRoomInfo = () => { renderRoomInfo = () => {
const { room, member } = this.state; const { room, member } = this.state;
const { const {
@ -433,7 +517,7 @@ class RoomActionsView extends React.Component {
rid, t, room, member rid, t, room, member
} }
})} })}
style={{ backgroundColor: themes[theme].backgroundColor }} style={({ pressed }) => [{ backgroundColor: pressed ? themes[theme].bannerBackground : themes[theme].backgroundColor }]}
accessibilityLabel={I18n.t('Room_Info')} accessibilityLabel={I18n.t('Room_Info')}
accessibilityTraits='button' accessibilityTraits='button'
enabled={!isGroupChat} enabled={!isGroupChat}
@ -568,9 +652,9 @@ class RoomActionsView extends React.Component {
<List.Section> <List.Section>
<List.Separator /> <List.Separator />
<List.Item <List.Item
title='Leave_channel' title={room.teamId && room.teamMain ? 'Leave' : 'Leave_channel'}
onPress={() => this.onPressTouchable({ onPress={() => this.onPressTouchable({
event: this.leaveChannel event: room.teamId && room.teamMain ? this.leaveTeam : this.leaveChannel
})} })}
testID='room-actions-leave-channel' testID='room-actions-leave-channel'
left={() => <List.Icon name='logout' color={themes[theme].dangerColor} />} left={() => <List.Icon name='logout' color={themes[theme].dangerColor} />}
@ -629,7 +713,7 @@ class RoomActionsView extends React.Component {
params: { params: {
rid, rid,
title: I18n.t('Add_users'), title: I18n.t('Add_users'),
nextAction: this.addUser nextAction: room.teamId ? this.addMemberToTeam : this.addUser
} }
})} })}
testID='room-actions-add-user' testID='room-actions-add-user'
@ -881,6 +965,7 @@ const mapStateToProps = state => ({
encryptionEnabled: state.encryption.enabled, encryptionEnabled: state.encryption.enabled,
serverVersion: state.server.version, serverVersion: state.server.version,
addUserToJoinedRoomPermission: state.permissions['add-user-to-joined-room'], addUserToJoinedRoomPermission: state.permissions['add-user-to-joined-room'],
addTeamMemberPermission: state.permissions['add-team-member'],
addUserToAnyCRoomPermission: state.permissions['add-user-to-any-c-room'], addUserToAnyCRoomPermission: state.permissions['add-user-to-any-c-room'],
addUserToAnyPRoomPermission: state.permissions['add-user-to-any-p-room'], addUserToAnyPRoomPermission: state.permissions['add-user-to-any-p-room'],
createInviteLinksPermission: state.permissions['create-invite-links'], createInviteLinksPermission: state.permissions['create-invite-links'],

View File

@ -8,6 +8,7 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import ImagePicker from 'react-native-image-crop-picker'; import ImagePicker from 'react-native-image-crop-picker';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { Q } from '@nozbe/watermelondb';
import { compareServerVersion, methods } from '../../lib/utils'; import { compareServerVersion, methods } from '../../lib/utils';
import database from '../../lib/database'; import database from '../../lib/database';
@ -41,6 +42,7 @@ const PERMISSION_ARCHIVE = 'archive-room';
const PERMISSION_UNARCHIVE = 'unarchive-room'; const PERMISSION_UNARCHIVE = 'unarchive-room';
const PERMISSION_DELETE_C = 'delete-c'; const PERMISSION_DELETE_C = 'delete-c';
const PERMISSION_DELETE_P = 'delete-p'; const PERMISSION_DELETE_P = 'delete-p';
const PERMISSION_EDIT_TEAM_CHANNEL = 'edit-team-channel';
class RoomInfoEditView extends React.Component { class RoomInfoEditView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
@ -48,6 +50,7 @@ class RoomInfoEditView extends React.Component {
}) })
static propTypes = { static propTypes = {
navigation: PropTypes.object,
route: PropTypes.object, route: PropTypes.object,
deleteRoom: PropTypes.func, deleteRoom: PropTypes.func,
serverVersion: PropTypes.string, serverVersion: PropTypes.string,
@ -58,7 +61,8 @@ class RoomInfoEditView extends React.Component {
archiveRoomPermission: PropTypes.array, archiveRoomPermission: PropTypes.array,
unarchiveRoomPermission: PropTypes.array, unarchiveRoomPermission: PropTypes.array,
deleteCPermission: PropTypes.array, deleteCPermission: PropTypes.array,
deletePPermission: PropTypes.array deletePPermission: PropTypes.array,
editTeamChannelPermission: PropTypes.array
}; };
constructor(props) { constructor(props) {
@ -100,7 +104,8 @@ class RoomInfoEditView extends React.Component {
archiveRoomPermission, archiveRoomPermission,
unarchiveRoomPermission, unarchiveRoomPermission,
deleteCPermission, deleteCPermission,
deletePPermission deletePPermission,
editTeamChannelPermission
} = this.props; } = this.props;
const rid = route.params?.rid; const rid = route.params?.rid;
if (!rid) { if (!rid) {
@ -116,25 +121,51 @@ class RoomInfoEditView extends React.Component {
this.init(this.room); this.init(this.room);
}); });
const result = await RocketChat.hasPermission([ let result;
setReadOnlyPermission,
setReactWhenReadOnlyPermission,
archiveRoomPermission,
unarchiveRoomPermission,
deleteCPermission,
deletePPermission
], rid);
this.setState({ if (this.room.teamId) {
permissions: { result = await RocketChat.hasPermission([
[PERMISSION_SET_READONLY]: result[0], setReadOnlyPermission,
[PERMISSION_SET_REACT_WHEN_READONLY]: result[1], setReactWhenReadOnlyPermission,
[PERMISSION_ARCHIVE]: result[2], archiveRoomPermission,
[PERMISSION_UNARCHIVE]: result[3], unarchiveRoomPermission,
[PERMISSION_DELETE_C]: result[4], deleteCPermission,
[PERMISSION_DELETE_P]: result[5] deletePPermission,
} editTeamChannelPermission
}); ], rid);
this.setState({
permissions: {
[PERMISSION_SET_READONLY]: result[0],
[PERMISSION_SET_REACT_WHEN_READONLY]: result[1],
[PERMISSION_ARCHIVE]: result[2],
[PERMISSION_UNARCHIVE]: result[3],
[PERMISSION_DELETE_C]: result[4],
[PERMISSION_DELETE_P]: result[5],
[PERMISSION_EDIT_TEAM_CHANNEL]: result[6]
}
});
} else {
result = await RocketChat.hasPermission([
setReadOnlyPermission,
setReactWhenReadOnlyPermission,
archiveRoomPermission,
unarchiveRoomPermission,
deleteCPermission,
deletePPermission
], rid);
this.setState({
permissions: {
[PERMISSION_SET_READONLY]: result[0],
[PERMISSION_SET_REACT_WHEN_READONLY]: result[1],
[PERMISSION_ARCHIVE]: result[2],
[PERMISSION_UNARCHIVE]: result[3],
[PERMISSION_DELETE_C]: result[4],
[PERMISSION_DELETE_P]: result[5]
}
});
}
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -284,13 +315,37 @@ class RoomInfoEditView extends React.Component {
}, 100); }, 100);
} }
deleteTeam = async(teamName) => {
const { navigation } = this.props;
const { room } = this.state;
try {
const result = await RocketChat.deleteTeam({ teamName });
if (result.success) {
const db = database.active;
const subCollection = db.get('subscriptions');
const teamChannels = await subCollection.query(
Q.and(Q.where('team_id', Q.eq(this.room.teamId), Q.where('name', Q.notEq(this.room.name))))
);
if (teamChannels.length) {
navigation.navigate('SelectListView', {
title: 'Delete_Team', teamChannels: this.teamChannels, teamName: room.name, subtitle: 'Select_Teams', delete: this.delete
});
} else {
navigation.navigate('RoomsListView');
}
}
} catch (e) {
log(e);
}
}
delete = () => { delete = () => {
const { room } = this.state; const { room } = this.state;
const { deleteRoom } = this.props; const { deleteRoom } = this.props;
Alert.alert( Alert.alert(
I18n.t('Are_you_sure_question_mark'), I18n.t('Confirmation'),
I18n.t('Delete_Room_Warning'), I18n.t('Delete_Team_Warning'),
[ [
{ {
text: I18n.t('Cancel'), text: I18n.t('Cancel'),
@ -299,7 +354,7 @@ class RoomInfoEditView extends React.Component {
{ {
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }), text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
style: 'destructive', style: 'destructive',
onPress: () => deleteRoom(room.rid, room.t) onPress: () => (this.room.teamId ? this.deleteTeam(room.name) : deleteRoom(room.rid, room.t))
} }
], ],
{ cancelable: false } { cancelable: false }
@ -678,7 +733,8 @@ const mapStateToProps = state => ({
archiveRoomPermission: state.permissions[PERMISSION_ARCHIVE], archiveRoomPermission: state.permissions[PERMISSION_ARCHIVE],
unarchiveRoomPermission: state.permissions[PERMISSION_UNARCHIVE], unarchiveRoomPermission: state.permissions[PERMISSION_UNARCHIVE],
deleteCPermission: state.permissions[PERMISSION_DELETE_C], deleteCPermission: state.permissions[PERMISSION_DELETE_C],
deletePPermission: state.permissions[PERMISSION_DELETE_P] deletePPermission: state.permissions[PERMISSION_DELETE_P],
editTeamChannelPermission: state.permissions[PERMISSION_EDIT_TEAM_CHANNEL]
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FlatList } from 'react-native'; import { FlatList, Alert } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import * as List from '../../containers/List'; import * as List from '../../containers/List';
@ -34,6 +34,7 @@ const PERMISSION_SET_LEADER = 'set-leader';
const PERMISSION_SET_OWNER = 'set-owner'; const PERMISSION_SET_OWNER = 'set-owner';
const PERMISSION_SET_MODERATOR = 'set-moderator'; const PERMISSION_SET_MODERATOR = 'set-moderator';
const PERMISSION_REMOVE_USER = 'remove-user'; const PERMISSION_REMOVE_USER = 'remove-user';
const PERMISSION_EDIT_TEAM_MEMBER = 'edit-team-member';
class RoomMembersView extends React.Component { class RoomMembersView extends React.Component {
static propTypes = { static propTypes = {
@ -55,7 +56,8 @@ class RoomMembersView extends React.Component {
setLeaderPermission: PropTypes.array, setLeaderPermission: PropTypes.array,
setOwnerPermission: PropTypes.array, setOwnerPermission: PropTypes.array,
setModeratorPermission: PropTypes.array, setModeratorPermission: PropTypes.array,
removeUserPermission: PropTypes.array removeUserPermission: PropTypes.array,
editTeamMemberPermission: PropTypes.array
} }
constructor(props) { constructor(props) {
@ -94,18 +96,44 @@ class RoomMembersView extends React.Component {
const { room } = this.state; const { room } = this.state;
const { const {
muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission, editTeamMemberPermission
} = this.props; } = this.props;
const result = await RocketChat.hasPermission([ let result;
muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission
], room.rid); if (room.teamId) {
result = await RocketChat.hasPermission([
muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission, editTeamMemberPermission
], room.rid);
this.permissions = {
[PERMISSION_MUTE_USER]: result[0],
[PERMISSION_SET_LEADER]: result[1],
[PERMISSION_SET_OWNER]: result[2],
[PERMISSION_SET_MODERATOR]: result[3],
[PERMISSION_REMOVE_USER]: result[4],
[PERMISSION_EDIT_TEAM_MEMBER]: result[5]
};
} else {
result = await RocketChat.hasPermission([
muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission
], room.rid);
this.permissions = {
[PERMISSION_MUTE_USER]: result[0],
[PERMISSION_SET_LEADER]: result[1],
[PERMISSION_SET_OWNER]: result[2],
[PERMISSION_SET_MODERATOR]: result[3],
[PERMISSION_REMOVE_USER]: result[4]
};
}
this.permissions = { this.permissions = {
[PERMISSION_MUTE_USER]: result[0], [PERMISSION_MUTE_USER]: result[0],
[PERMISSION_SET_LEADER]: result[1], [PERMISSION_SET_LEADER]: result[1],
[PERMISSION_SET_OWNER]: result[2], [PERMISSION_SET_OWNER]: result[2],
[PERMISSION_SET_MODERATOR]: result[3], [PERMISSION_SET_MODERATOR]: result[3],
[PERMISSION_REMOVE_USER]: result[4] [PERMISSION_REMOVE_USER]: result[4],
[PERMISSION_EDIT_TEAM_MEMBER]: result[5]
}; };
const hasSinglePermission = Object.values(this.permissions).some(p => !!p); const hasSinglePermission = Object.values(this.permissions).some(p => !!p);
@ -163,6 +191,56 @@ class RoomMembersView extends React.Component {
} }
} }
handleRemoveFromTeam = async(selectedUser) => {
const { navigation } = this.props;
const { room } = this.state;
const db = database.active;
const subCollection = db.get('subscriptions');
const teamChannels = await subCollection.query(
Q.where('team_id', Q.eq(room.teamId))
);
if (teamChannels) {
navigation.navigate('SelectListView', {
title: 'Remove_Member', subtitle: 'Remove_User_Teams', teamChannels, selectedUser
});
} else {
Alert.alert(
I18n.t('Confirmation'),
I18n.t('Removing_user_from_this_Team', { user: selectedUser.username }),
[
{
text: I18n.t('Cancel'),
style: 'cancel'
},
{
text: I18n.t('Yes_action_it', { action: I18n.t('remove') }),
style: 'destructive',
onPress: () => this.removeFromTeam(selectedUser)
}
],
{ cancelable: false }
);
}
}
removeFromTeam = async(selectedUser) => {
try {
const { members, membersFiltered, room } = this.state;
const userId = selectedUser._id;
const result = await RocketChat.removeTeamMember({ teamName: room.name, userId });
if (result.success) {
const message = I18n.t('User_has_been_removed_from_s', { s: RocketChat.getRoomTitle(room) });
EventEmitter.emit(LISTENER, { message });
this.setState({
members: members.filter(member => member._id !== userId),
membersFiltered: membersFiltered.filter(member => member._id !== userId)
});
}
} catch (e) {
log(e);
}
}
onPressUser = (selectedUser) => { onPressUser = (selectedUser) => {
const { room } = this.state; const { room } = this.state;
const { showActionSheet, user } = this.props; const { showActionSheet, user } = this.props;
@ -173,6 +251,46 @@ class RoomMembersView extends React.Component {
onPress: () => this.navToDirectMessage(selectedUser) onPress: () => this.navToDirectMessage(selectedUser)
}]; }];
// Ignore
if (selectedUser._id !== user.id) {
const { ignored } = room;
const isIgnored = ignored?.includes?.(selectedUser._id);
options.push({
icon: 'ignore',
title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
onPress: () => this.handleIgnore(selectedUser, !isIgnored)
});
}
if (this.permissions['mute-user']) {
const { muted = [] } = room;
const userIsMuted = muted.find?.(m => m === selectedUser.username);
selectedUser.muted = !!userIsMuted;
options.push({
icon: userIsMuted ? 'audio' : 'audio-disabled',
title: I18n.t(userIsMuted ? 'Unmute' : 'Mute'),
onPress: () => {
showConfirmationAlert({
message: I18n.t(`The_user_${ userIsMuted ? 'will' : 'wont' }_be_able_to_type_in_roomName`, {
roomName: RocketChat.getRoomTitle(room)
}),
confirmationText: I18n.t(userIsMuted ? 'Unmute' : 'Mute'),
onPress: () => this.handleMute(selectedUser)
});
}
});
}
// Remove from team
if (this.permissions['edit-team-member']) {
options.push({
icon: 'close',
danger: true,
title: I18n.t('Remove_from_Team'),
onPress: () => this.handleRemoveFromTeam(selectedUser)
});
}
// Owner // Owner
if (this.permissions['set-owner']) { if (this.permissions['set-owner']) {
const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id);
@ -206,36 +324,6 @@ class RoomMembersView extends React.Component {
}); });
} }
// Ignore
if (selectedUser._id !== user.id) {
const { ignored } = room;
const isIgnored = ignored?.includes?.(selectedUser._id);
options.push({
icon: 'ignore',
title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
onPress: () => this.handleIgnore(selectedUser, !isIgnored)
});
}
if (this.permissions['mute-user']) {
const { muted = [] } = room;
const userIsMuted = muted.find?.(m => m === selectedUser.username);
selectedUser.muted = !!userIsMuted;
options.push({
icon: userIsMuted ? 'audio' : 'audio-disabled',
title: I18n.t(userIsMuted ? 'Unmute' : 'Mute'),
onPress: () => {
showConfirmationAlert({
message: I18n.t(`The_user_${ userIsMuted ? 'will' : 'wont' }_be_able_to_type_in_roomName`, {
roomName: RocketChat.getRoomTitle(room)
}),
confirmationText: I18n.t(userIsMuted ? 'Unmute' : 'Mute'),
onPress: () => this.handleMute(selectedUser)
});
}
});
}
// Remove from room // Remove from room
if (this.permissions['remove-user']) { if (this.permissions['remove-user']) {
options.push({ options.push({
@ -292,6 +380,7 @@ class RoomMembersView extends React.Component {
this.setState({ isLoading: true }); this.setState({ isLoading: true });
try { try {
const membersResult = await RocketChat.getRoomMembers(rid, allUsers, members.length, PAGE_SIZE); const membersResult = await RocketChat.getRoomMembers(rid, allUsers, members.length, PAGE_SIZE);
console.log({ membersResult });
const newMembers = membersResult.records; const newMembers = membersResult.records;
this.setState({ this.setState({
members: members.concat(newMembers || []), members: members.concat(newMembers || []),
@ -477,7 +566,8 @@ const mapStateToProps = state => ({
setLeaderPermission: state.permissions[PERMISSION_SET_LEADER], setLeaderPermission: state.permissions[PERMISSION_SET_LEADER],
setOwnerPermission: state.permissions[PERMISSION_SET_OWNER], setOwnerPermission: state.permissions[PERMISSION_SET_OWNER],
setModeratorPermission: state.permissions[PERMISSION_SET_MODERATOR], setModeratorPermission: state.permissions[PERMISSION_SET_MODERATOR],
removeUserPermission: state.permissions[PERMISSION_REMOVE_USER] removeUserPermission: state.permissions[PERMISSION_REMOVE_USER],
editTeamMemberPermission: state.permissions['edit-team-member']
}); });
export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView))); export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView)));

View File

@ -18,7 +18,8 @@ class RightButtonsContainer extends Component {
teamId: PropTypes.bool, teamId: PropTypes.bool,
navigation: PropTypes.object, navigation: PropTypes.object,
isMasterDetail: PropTypes.bool, isMasterDetail: PropTypes.bool,
toggleFollowThread: PropTypes.func toggleFollowThread: PropTypes.func,
joined: PropTypes.bool
}; };
constructor(props) { constructor(props) {
@ -113,7 +114,7 @@ class RightButtonsContainer extends Component {
goTeamChannels = () => { goTeamChannels = () => {
logEvent(events.ROOM_GO_TEAM_CHANNELS); logEvent(events.ROOM_GO_TEAM_CHANNELS);
const { const {
navigation, isMasterDetail, teamId navigation, isMasterDetail, teamId, rid
} = this.props; } = this.props;
if (isMasterDetail) { if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', { navigation.navigate('ModalStackNavigator', {
@ -121,7 +122,7 @@ class RightButtonsContainer extends Component {
params: { teamId } params: { teamId }
}); });
} else { } else {
navigation.navigate('TeamChannelsView', { teamId }); navigation.navigate('TeamChannelsView', { teamId, rid });
} }
} }
@ -163,7 +164,7 @@ class RightButtonsContainer extends Component {
isFollowingThread, tunread, tunreadUser, tunreadGroup isFollowingThread, tunread, tunreadUser, tunreadGroup
} = this.state; } = this.state;
const { const {
t, tmid, threadsEnabled, teamId t, tmid, threadsEnabled, teamId, joined
} = this.props; } = this.props;
if (t === 'l') { if (t === 'l') {
return null; return null;
@ -181,7 +182,7 @@ class RightButtonsContainer extends Component {
} }
return ( return (
<HeaderButton.Container> <HeaderButton.Container>
{teamId ? ( {teamId && joined ? (
<HeaderButton.Item <HeaderButton.Item
iconName='channel-public' iconName='channel-public'
onPress={this.goTeamChannels} onPress={this.goTeamChannels}

View File

@ -301,7 +301,7 @@ class RoomView extends React.Component {
setHeader = () => { setHeader = () => {
const { const {
room, unreadsCount, roomUserId room, unreadsCount, roomUserId, joined
} = this.state; } = this.state;
const { const {
navigation, isMasterDetail, theme, baseUrl, user, insets, route navigation, isMasterDetail, theme, baseUrl, user, insets, route
@ -331,7 +331,7 @@ class RoomView extends React.Component {
let numIconsRight = 2; let numIconsRight = 2;
if (tmid) { if (tmid) {
numIconsRight = 1; numIconsRight = 1;
} else if (teamId) { } else if (teamId && joined) {
numIconsRight = 3; numIconsRight = 3;
} }
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight }); const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight });
@ -380,6 +380,7 @@ class RoomView extends React.Component {
rid={rid} rid={rid}
tmid={tmid} tmid={tmid}
teamId={teamId} teamId={teamId}
joined={joined}
t={t} t={t}
navigation={navigation} navigation={navigation}
toggleFollowThread={this.toggleFollowThread} toggleFollowThread={this.toggleFollowThread}
@ -1032,6 +1033,7 @@ class RoomView extends React.Component {
renderActions = () => { renderActions = () => {
const { room, readOnly } = this.state; const { room, readOnly } = this.state;
const { user } = this.props; const { user } = this.props;
return ( return (
<> <>
<MessageActions <MessageActions

View File

@ -894,7 +894,6 @@ class RoomsListView extends React.Component {
return this.renderSectionHeader(item.rid); return this.renderSectionHeader(item.rid);
} }
const { item: currentItem } = this.state;
const { const {
user: { username }, user: { username },
StoreLastMessage, StoreLastMessage,
@ -925,7 +924,6 @@ class RoomsListView extends React.Component {
getIsGroupChat={this.isGroupChat} getIsGroupChat={this.isGroupChat}
getIsRead={this.isRead} getIsRead={this.isRead}
visitor={item.visitor} visitor={item.visitor}
isFocused={currentItem?.rid === item.rid}
/> />
); );
}; };
@ -948,7 +946,6 @@ class RoomsListView extends React.Component {
if (loading) { if (loading) {
return <ActivityIndicator theme={theme} />; return <ActivityIndicator theme={theme} />;
} }
return ( return (
<FlatList <FlatList
ref={this.getScrollRef} ref={this.getScrollRef}

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

@ -0,0 +1,299 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import React from 'react';
import PropTypes from 'prop-types';
import {
View, StyleSheet, FlatList, Text, Alert, Pressable
} from 'react-native';
import { connect } from 'react-redux';
import { HeaderBackButton } from '@react-navigation/stack';
import * as List from '../containers/List';
import Touch from '../utils/touch';
import { leaveRoom as leaveRoomAction } from '../actions/room';
import RocketChat from '../lib/rocketchat';
import sharedStyles from './Styles';
import I18n from '../i18n';
import { CustomIcon } from '../lib/Icons';
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';
import { LISTENER } from '../containers/Toast';
import EventEmitter from '../utils/events';
import log from '../utils/log';
const styles = StyleSheet.create({
button: {
height: 46,
flexDirection: 'row',
alignItems: 'center'
},
buttonIcon: {
marginLeft: 18,
marginRight: 16
},
buttonText: {
fontSize: 17,
...sharedStyles.textRegular
},
textContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
marginRight: 15
},
icon: {
marginHorizontal: 15,
alignSelf: 'center'
}
});
class SelectListView extends React.Component {
static propTypes = {
navigation: PropTypes.object,
route: PropTypes.object,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
}),
theme: PropTypes.string,
isMasterDetail: PropTypes.bool,
leaveRoom: PropTypes.func
};
constructor(props) {
super(props);
const teamChannels = props.route?.params?.teamChannels;
this.title = props.route?.params?.title;
this.subtitle = props.route?.params?.subtitle;
this.teamName = props.route?.params?.teamName;
this.room = props.route?.params?.room;
this.delete = props.route?.params?.delete;
this.state = {
data: teamChannels,
selected: [],
loading: false
};
this.setHeader();
}
setHeader = () => {
const { navigation, isMasterDetail, theme } = this.props;
const options = {
headerShown: true,
headerTitleAlign: 'center',
headerTitle: I18n.t(this.title)
};
if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
} else {
options.headerLeft = () => <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />;
}
options.headerRight = () => (
<HeaderButton.Container>
<HeaderButton.Item title={I18n.t('Next')} onPress={this.delete ? this.delete : this.submit} testID='select-list-view-submit' />
</HeaderButton.Container>
);
navigation.setOptions(options);
}
submit = async() => {
const { selected } = this.state;
const { navigation, leaveRoom } = this.props;
this.setState({ loading: true });
try {
// logEvent(events.CT_ADD_ROOM_TO_TEAM);
const result = await RocketChat.leaveTeam({ teamName: this.teamName });
if (selected) {
selected.map(room => leaveRoom(room.rid, room.t));
}
if (result.success) {
this.setState({ loading: false });
navigation.navigate('RoomsListView');
}
} catch (e) {
// logEvent(events.CT_ADD_ROOM_TO_TEAM_F);
this.setState({ loading: false });
Alert.alert(
I18n.t('Cannot_leave'),
I18n.t(e.data.error),
[
{
text: 'OK',
style: 'cancel'
}
]
);
}
}
renderHeader = () => {
const { theme } = this.props;
return (
<View style={{ backgroundColor: themes[theme].backgroundColor }}>
<Text style={[styles.buttonText, { color: themes[theme].bodyText, margin: 16 }]}>{I18n.t('Select_Teams')}</Text>
</View>
);
}
showAlert = () => {
Alert.alert(
I18n.t('Cannot_leave'),
I18n.t('Last_owner_team_room'),
[
{
text: 'OK',
style: 'cancel'
}
]
);
}
leaveChannel = () => {
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)
}
]
);
}
removeFromTeam = async(selectedUser) => {
try {
const { data, room } = this.state;
const userId = selectedUser._id;
const result = await RocketChat.removeTeamMember({ teamName: room.name, userId });
if (result.success) {
const message = I18n.t('User_has_been_removed_from_s', { s: RocketChat.getRoomTitle(room) });
EventEmitter.emit(LISTENER, { message });
this.setState({ data: data.filter(member => member._id !== userId) });
}
} catch (e) {
log(e);
}
}
renderChannel = ({
onPress, testID, title, icon, checked, alert
}) => {
const { theme } = this.props;
return (
<Touch
onPress={onPress}
style={{ backgroundColor: themes[theme].backgroundColor }}
testID={testID}
theme={theme}
>
<View style={[styles.button, { borderColor: themes[theme].separatorColor, marginVertical: 4 }]}>
<CustomIcon style={[styles.buttonIcon, { color: themes[theme].controlText }]} size={24} name={icon} />
<View style={styles.textContainer}>
<Text style={[styles.buttonText, { color: themes[theme].bodyText }]}>{title}</Text>
{ alert
? (
<Pressable onPress={() => this.showAlert()}>
<CustomIcon style={[styles.buttonIcon, { color: themes[theme].dangerColor, transform: [{ rotateY: '180deg' }] }]} size={24} name={alert} />
</Pressable>
) : null}
</View>
{checked ? <CustomIcon name={checked} size={22} style={[styles.icon, { color: themes[theme].actionTintColor }]} /> : null}
</View>
</Touch>
);
}
isChecked = (rid) => {
const { selected } = this.state;
return selected.includes(rid);
}
toggleChannel = (rid, roles) => {
const { selected } = this.state;
if (roles) {
this.showAlert();
return;
}
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 }) => (
<>
{this.renderChannel({
onPress: () => this.toggleChannel(item.rid, item.roles),
title: item.name,
icon: item.t === 'p' ? 'channel-private' : 'channel-public',
checked: this.isChecked(item.rid, item.roles) ? 'check' : null,
testID: 'select-list-view-item',
alert: item.roles ? 'info' : null
})}
</>
)
renderList = () => {
const { data } = this.state;
const { theme } = this.props;
return (
<FlatList
data={data}
extraData={this.state}
keyExtractor={item => item._id}
renderItem={this.renderItem}
ListHeaderComponent={this.renderHeader}
ItemSeparatorComponent={List.Separator}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always'
/>
);
}
render() {
const { loading } = this.state;
return (
<SafeAreaView testID='new-message-view'>
<StatusBar />
{this.renderList()}
<Loading visible={loading} />
</SafeAreaView>
);
}
}
const mapStateToProps = state => ({
isMasterDetail: state.app.isMasterDetail
});
const mapDispatchToProps = dispatch => ({
leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(SelectListView));

View File

@ -50,7 +50,7 @@ class SelectedUsersView extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.init(); this.init();
this.flatlist = React.createRef();
const maxUsers = props.route.params?.maxUsers; const maxUsers = props.route.params?.maxUsers;
this.state = { this.state = {
maxUsers, maxUsers,
@ -190,9 +190,13 @@ class SelectedUsersView extends React.Component {
if (users.length === 0) { if (users.length === 0) {
return null; return null;
} }
const ITEM_WIDTH = 250;
return ( return (
<FlatList <FlatList
data={users} data={users}
ref={ref => this.flatlist = ref}
onContentSizeChange={() => this.flatlist.scrollToEnd()}
getItemLayout={(_, index) => ({ length: ITEM_WIDTH, offset: ITEM_WIDTH * index, index })}
keyExtractor={item => item._id} keyExtractor={item => item._id}
style={[sharedStyles.separatorTop, { borderColor: themes[theme].separatorColor }]} style={[sharedStyles.separatorTop, { borderColor: themes[theme].separatorColor }]}
contentContainerStyle={{ marginVertical: 5 }} contentContainerStyle={{ marginVertical: 5 }}

View File

@ -15,7 +15,7 @@ const Item = React.memo(({
testID={testID} testID={testID}
onPress={onPress} onPress={onPress}
theme={theme} theme={theme}
style={[styles.item, current && { backgroundColor: themes[theme].borderColor }]} style={({ pressed }) => [styles.item, current && { backgroundColor: pressed ? themes[theme].borderColor : themes[theme].bannerBackground }]}
> >
<View style={styles.itemHorizontal}> <View style={styles.itemHorizontal}>
{left} {left}

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Keyboard } from 'react-native'; import { Keyboard, Alert } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { withSafeAreaInsets } from 'react-native-safe-area-context';
@ -28,6 +28,8 @@ import debounce from '../utils/debounce';
import { showErrorAlert } from '../utils/info'; import { showErrorAlert } from '../utils/info';
import { goRoom } from '../utils/goRoom'; import { goRoom } from '../utils/goRoom';
import I18n from '../i18n'; import I18n from '../i18n';
import { withActionSheet } from '../containers/ActionSheet';
import { deleteRoom as deleteRoomAction } from '../actions/room';
const API_FETCH_COUNT = 25; const API_FETCH_COUNT = 25;
@ -47,12 +49,17 @@ class TeamChannelsView extends React.Component {
theme: PropTypes.string, theme: PropTypes.string,
useRealName: PropTypes.bool, useRealName: PropTypes.bool,
width: PropTypes.number, width: PropTypes.number,
StoreLastMessage: PropTypes.bool StoreLastMessage: PropTypes.bool,
addTeamChannelPermission: PropTypes.array,
removeTeamChannelPermission: PropTypes.array,
showActionSheet: PropTypes.func,
deleteRoom: PropTypes.func
} }
constructor(props) { constructor(props) {
super(props); super(props);
this.teamId = props.route.params?.teamId; this.teamId = props.route.params?.teamId;
this.rid = props.route.params?.rid;
this.state = { this.state = {
loading: true, loading: true,
loadingMore: false, loadingMore: false,
@ -60,9 +67,11 @@ class TeamChannelsView extends React.Component {
isSearching: false, isSearching: false,
searchText: '', searchText: '',
search: [], search: [],
end: false end: false,
showCreate: false
}; };
this.loadTeam(); this.loadTeam();
this.setHeader();
} }
componentDidMount() { componentDidMount() {
@ -70,6 +79,9 @@ class TeamChannelsView extends React.Component {
} }
loadTeam = async() => { loadTeam = async() => {
const { addTeamChannelPermission } = this.props;
const { loading } = this.state;
const db = database.active; const db = database.active;
try { try {
const subCollection = db.get('subscriptions'); const subCollection = db.get('subscriptions');
@ -82,6 +94,14 @@ class TeamChannelsView extends React.Component {
if (!this.team) { if (!this.team) {
throw new Error(); throw new Error();
} }
const permissions = await RocketChat.hasPermission([addTeamChannelPermission], this.team.rid);
if (permissions[0]) {
this.setState({ showCreate: true }, () => this.setHeader());
}
if (loading) {
this.setState({ loading: false });
}
} catch { } catch {
const { navigation } = this.props; const { navigation } = this.props;
navigation.pop(); navigation.pop();
@ -135,8 +155,8 @@ class TeamChannelsView extends React.Component {
} }
}, 300) }, 300)
getHeader = () => { setHeader = () => {
const { isSearching } = this.state; const { isSearching, showCreate, data } = this.state;
const { const {
navigation, isMasterDetail, insets, theme navigation, isMasterDetail, insets, theme
} = this.props; } = this.props;
@ -146,7 +166,7 @@ class TeamChannelsView extends React.Component {
return; return;
} }
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 }); const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 2 });
if (isSearching) { if (isSearching) {
return { return {
@ -201,14 +221,11 @@ class TeamChannelsView extends React.Component {
options.headerRight = () => ( options.headerRight = () => (
<HeaderButton.Container> <HeaderButton.Container>
<HeaderButton.Item iconName='search' onPress={this.onSearchPress} /> <HeaderButton.Item iconName='search' onPress={this.onSearchPress} />
{ showCreate
? <HeaderButton.Item iconName='create' onPress={() => navigation.navigate('AddChannelTeamView', { teamId: this.teamId, teamChannels: data })} />
: null}
</HeaderButton.Container> </HeaderButton.Container>
); );
return options;
}
setHeader = () => {
const { navigation } = this.props;
const options = this.getHeader();
navigation.setOptions(options); navigation.setOptions(options);
} }
@ -287,6 +304,93 @@ class TeamChannelsView extends React.Component {
} }
}, 1000, true); }, 1000, true);
options = item => ([
{
title: I18n.t('Auto-join'),
icon: item.t === 'p' ? 'channel-private' : 'channel-public'
// onPress: this.autoJoin
},
{
title: I18n.t('Remove_from_Team'),
icon: 'close',
danger: true,
onPress: () => this.remove(item)
},
{
title: I18n.t('Delete'),
icon: 'delete',
danger: true,
onPress: () => this.delete(item)
}
])
remove = (item) => {
Alert.alert(
I18n.t('Confirmation'),
I18n.t('Delete_Team_Room_Warning'),
[
{
text: I18n.t('Cancel'),
style: 'cancel'
},
{
text: I18n.t('Yes_action_it', { action: I18n.t('remove') }),
style: 'destructive',
onPress: () => this.removeRoom(item)
}
],
{ cancelable: false }
);
}
removeRoom = async(item) => {
try {
const { data } = this.state;
const result = await RocketChat.removeTeamRoom({ roomId: item.rid, teamId: this.team.teamId });
if (result.success) {
const newData = data.filter(room => result.room._id !== room.rid);
this.setState({ loading: true, data: newData }, () => {
this.load();
this.loadTeam();
});
}
} catch (e) {
log(e);
}
}
delete = (item) => {
const { deleteRoom } = this.props;
Alert.alert(
I18n.t('Are_you_sure_question_mark'),
I18n.t('Delete_Room_Warning'),
[
{
text: I18n.t('Cancel'),
style: 'cancel'
},
{
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
style: 'destructive',
onPress: () => deleteRoom(item.rid, item.t)
}
],
{ cancelable: false }
);
}
showChannelActions = async(item) => {
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
const { showActionSheet, removeTeamChannelPermission } = this.props;
const permissions = await RocketChat.hasPermission([removeTeamChannelPermission], this.team.rid);
if (!permissions[0]) {
return;
}
showActionSheet({ options: this.options(item) });
}
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { const {
StoreLastMessage, StoreLastMessage,
@ -302,6 +406,7 @@ class TeamChannelsView extends React.Component {
showLastMessage={StoreLastMessage} showLastMessage={StoreLastMessage}
onPress={this.onPressItem} onPress={this.onPressItem}
width={width} width={width}
onLongPress={this.showChannelActions}
useRealName={useRealName} useRealName={useRealName}
getRoomTitle={this.getRoomTitle} getRoomTitle={this.getRoomTitle}
getRoomAvatar={this.getRoomAvatar} getRoomAvatar={this.getRoomAvatar}
@ -365,7 +470,13 @@ const mapStateToProps = state => ({
user: getUserSelector(state), user: getUserSelector(state),
useRealName: state.settings.UI_Use_Real_Name, useRealName: state.settings.UI_Use_Real_Name,
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
StoreLastMessage: state.settings.Store_Last_Message StoreLastMessage: state.settings.Store_Last_Message,
addTeamChannelPermission: state.permissions['add-team-channel'],
removeTeamChannelPermission: state.permissions['remove-team-channel']
}); });
export default connect(mapStateToProps)(withDimensions(withSafeAreaInsets(withTheme(TeamChannelsView)))); const mapDispatchToProps = dispatch => ({
deleteRoom: (rid, t) => dispatch(deleteRoomAction(rid, t))
});
export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withSafeAreaInsets(withTheme(withActionSheet(TeamChannelsView)))));

View File

@ -26,7 +26,7 @@ const styles = StyleSheet.create({
const DropdownItem = React.memo(({ const DropdownItem = React.memo(({
theme, onPress, iconName, text theme, onPress, iconName, text
}) => ( }) => (
<Touch theme={theme} onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }}> <Touch theme={theme} onPress={onPress} style={({ pressed }) => [{ backgroundColor: pressed ? themes[theme].bannerBackground : themes[theme].backgroundColor }]}>
<View style={styles.container}> <View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{text}</Text> <Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{text}</Text>
{iconName ? <CustomIcon name={iconName} size={22} color={themes[theme].auxiliaryText} /> : null} {iconName ? <CustomIcon name={iconName} size={22} color={themes[theme].auxiliaryText} /> : null}

View File

@ -42,6 +42,11 @@ const data = {
name: `detox-private-${ value }` name: `detox-private-${ value }`
} }
}, },
teams: {
private: {
name: `detox-team-${ value }`
}
},
registeringUser: { registeringUser: {
username: `newuser${ value }`, username: `newuser${ value }`,
password: `password${ value }`, password: `password${ value }`,

View File

@ -57,6 +57,26 @@ const createChannelIfNotExists = async (channelname) => {
} }
} }
const createTeamIfNotExists = async (teamname) => {
console.log(`Creating private team ${teamname}`)
try {
const team = await rocketchat.post('teams.create', {
"name": teamname,
"type": 1
})
return team
} catch (createError) {
try { //Maybe it exists already?
const team = rocketchat.get(`teams.info?teamName=${teamname}`)
return team
} catch (infoError) {
console.log(JSON.stringify(createError))
console.log(JSON.stringify(infoError))
throw "Failed to find or create private team"
}
}
}
const createGroupIfNotExists = async (groupname) => { const createGroupIfNotExists = async (groupname) => {
console.log(`Creating private group ${groupname}`) console.log(`Creating private group ${groupname}`)
try { try {
@ -133,6 +153,13 @@ const setup = async () => {
} }
} }
for (var teamKey in data.teams) {
if (data.teams.hasOwnProperty(teamKey)) {
const team = data.teams[teamKey]
await createTeamIfNotExists(team.name)
}
}
return return
} }

View File

@ -0,0 +1,82 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('../../data');
const { tapBack, sleep, navigateToLogin, login, tryTapping } = require('../../helpers/app');
describe('Create team screen', () => {
before(async() => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password);
});
describe('New Message', async() => {
before(async() => {
await element(by.id('rooms-list-view-create-channel')).tap();
});
describe('Render', async() => {
it('should have team button', async() => {
await waitFor(element(by.id('new-message-view-create-team'))).toBeVisible().withTimeout(2000);
});
})
describe('Usage', async() => {
it('should navigate to select users', async() => {
await element(by.id('new-message-view-create-team')).tap();
await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000);
});
})
});
describe('Select Users', async() => {
it('should search users', async() => {
await element(by.id('select-users-view-search')).replaceText('rocket.cat');
await waitFor(element(by.id(`select-users-view-item-rocket.cat`))).toBeVisible().withTimeout(10000);
});
it('should select/unselect user', async() => {
// Spotlight issues
await element(by.id('select-users-view-item-rocket.cat')).tap();
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000);
await element(by.id('selected-user-rocket.cat')).tap();
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeNotVisible().withTimeout(10000);
// Spotlight issues
await element(by.id('select-users-view-item-rocket.cat')).tap();
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000);
});
it('should create team', async() => {
await element(by.id('selected-users-view-submit')).tap();
await waitFor(element(by.id('create-team-view'))).toExist().withTimeout(10000);
});
})
describe('Create Team', async() => {
describe('Usage', async() => {
it('should get invalid team name', async() => {
await element(by.id('create-team-name')).typeText(`${data.teams.private.name}`);
await element(by.id('create-channel-submit')).tap();
await element(by.text('OK')).tap();
});
it('should create private team', async() => {
const room = `private${ data.random }`;
await element(by.id('create-team-name')).replaceText('');
await element(by.id('create-team-name')).typeText(room);
await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000);
await expect(element(by.id('room-view'))).toExist();
await waitFor(element(by.id(`room-view-title-${ room }`))).toExist().withTimeout(6000);
await expect(element(by.id(`room-view-title-${ room }`))).toExist();
await tapBack();
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000);
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(6000);
await expect(element(by.id(`rooms-list-view-item-${ room }`))).toExist();
});
})
});
});