From cbc6892084d967224ed2576ea848c63afa6bbad5 Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Fri, 26 Aug 2022 10:21:25 -0300 Subject: [PATCH] [NEW] Unify members section (#4399) * create useUserPermissions hook * create CheckRadioButton component * fix return * create MembersSection component * apply MembersSection and header filter * fix re-render and testID * fix detox tests * rename to RadioButton * move the component closer to the screen * remove useUserPermissions * remove theme prop * migrate to hooks * fix team permissions * remove theme prop from UserItem * remove options prop * fix Member * remove commented test * fixes * fix for room not joined * add room members events * adds empty option * add members filter and pagination * clear RoomMembersView * remove unused styles * Update app/views/RoomMembersView/index.tsx Co-authored-by: Diego Mello * wip * Temp workaround for SearchBox background color * Rename import * Fix missing params for 5.0 * Fix e2e tests Co-authored-by: Diego Mello --- app/containers/RadioButton/index.tsx | 16 + app/containers/UserItem.tsx | 57 +- app/i18n/locales/en.json | 1 + app/i18n/locales/pt-BR.json | 1 + app/lib/methods/helpers/helpers.ts | 2 +- app/lib/methods/helpers/log/events.ts | 4 + app/lib/navigation/appNavigation.ts | 7 +- app/stacks/InsideStack.tsx | 2 +- app/stacks/MasterDetailStack/index.tsx | 2 +- app/stacks/MasterDetailStack/types.ts | 1 + app/stacks/types.ts | 1 + app/views/CreateChannelView.tsx | 23 +- app/views/NewMessageView.tsx | 1 - app/views/RoomActionsView/index.tsx | 93 +-- .../components/ActionsSection.tsx | 109 +++ app/views/RoomMembersView/helpers.ts | 258 ++++++ app/views/RoomMembersView/index.tsx | 767 ++++++------------ app/views/RoomMembersView/styles.ts | 19 +- app/views/SelectedUsersView.tsx | 23 +- e2e/tests/room/03-roomactions.spec.js | 67 +- e2e/tests/team/02-team.spec.js | 19 +- 21 files changed, 752 insertions(+), 721 deletions(-) create mode 100644 app/containers/RadioButton/index.tsx create mode 100644 app/views/RoomMembersView/components/ActionsSection.tsx create mode 100644 app/views/RoomMembersView/helpers.ts diff --git a/app/containers/RadioButton/index.tsx b/app/containers/RadioButton/index.tsx new file mode 100644 index 000000000..b9c8efe5c --- /dev/null +++ b/app/containers/RadioButton/index.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { RadioButton as RadioButtonUiLib } from 'react-native-ui-lib'; + +import { useTheme } from '../../theme'; + +export const RadioButton = ({ check, testID, size }: { check: boolean; testID?: string; size?: number }): React.ReactElement => { + const { colors } = useTheme(); + return ( + + ); +}; diff --git a/app/containers/UserItem.tsx b/app/containers/UserItem.tsx index b39948856..e044dd6ba 100644 --- a/app/containers/UserItem.tsx +++ b/app/containers/UserItem.tsx @@ -4,9 +4,8 @@ import { Pressable, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-n import Avatar from './Avatar'; import { CustomIcon, TIconsName } from './CustomIcon'; import sharedStyles from '../views/Styles'; -import { themes } from '../lib/constants'; import { isIOS } from '../lib/methods/helpers'; -import { TSupportedThemes } from '../theme'; +import { useTheme } from '../theme'; const styles = StyleSheet.create({ button: { @@ -47,34 +46,36 @@ interface IUserItem { onLongPress?: () => void; style?: StyleProp; icon?: TIconsName | null; - theme: TSupportedThemes; } -const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, theme }: IUserItem) => ( - ({ - backgroundColor: isIOS && pressed ? themes[theme].bannerBackground : 'transparent' - })} - > - - - - - {name} - - - @{username} - +const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon }: IUserItem): React.ReactElement => { + const { colors } = useTheme(); + return ( + ({ + backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent' + })} + > + + + + + {name} + + + @{username} + + + {icon ? : null} - {icon ? : null} - - -); + + ); +}; export default UserItem; diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index c865bbc37..5f0c0c134 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -356,6 +356,7 @@ "No_mentioned_messages": "No mentioned messages", "No_pinned_messages": "No pinned messages", "No_results_found": "No results found", + "No_members_found": "No members found", "No_starred_messages": "No starred messages", "No_thread_messages": "No thread messages", "No_label_provided": "No {{label}} provided.", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index e30e7ad6c..694aef0d6 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -334,6 +334,7 @@ "No_mentioned_messages": "Não há menções", "No_pinned_messages": "Não há mensagens fixadas", "No_results_found": "Nenhum resultado encontrado", + "No_members_found": "Nenhum usuário encontrado", "No_starred_messages": "Não há mensagens favoritas", "No_thread_messages": "Não há tópicos", "No_label_provided": "Sem {{label}}.", diff --git a/app/lib/methods/helpers/helpers.ts b/app/lib/methods/helpers/helpers.ts index d496b7167..bfacfc015 100644 --- a/app/lib/methods/helpers/helpers.ts +++ b/app/lib/methods/helpers/helpers.ts @@ -85,7 +85,7 @@ export function hasRole(role): boolean { return userRoles.indexOf(role) > -1; } -export async function hasPermission(permissions, rid?: any): boolean[] { +export async function hasPermission(permissions, rid?: any): Promise { let roomRoles = []; if (rid) { const db = database.active; diff --git a/app/lib/methods/helpers/log/events.ts b/app/lib/methods/helpers/log/events.ts index ea4e57f48..b4562b6c5 100644 --- a/app/lib/methods/helpers/log/events.ts +++ b/app/lib/methods/helpers/log/events.ts @@ -272,6 +272,10 @@ export default { RA_MOVE_TO_TEAM_F: 'ra_move_to_team_f', RA_SEARCH_TEAM: 'ra_search_team', + // ROOM MEMBERS ACTIONS VIEW + RM_GO_SELECTEDUSERS: 'rm_go_selected_users', + RM_GO_INVITEUSERS: 'rm_go_invite_users', + // ROOM INFO VIEW RI_GO_RI_EDIT: 'ri_go_ri_edit', RI_GO_LIVECHAT_EDIT: 'ri_go_livechat_edit', diff --git a/app/lib/navigation/appNavigation.ts b/app/lib/navigation/appNavigation.ts index c2ef51c75..7d2f73756 100644 --- a/app/lib/navigation/appNavigation.ts +++ b/app/lib/navigation/appNavigation.ts @@ -17,10 +17,15 @@ function replace(name: string, params: any) { navigationRef.current?.dispatch(StackActions.replace(name, params)); } +function popToTop() { + navigationRef.current?.dispatch(StackActions.popToTop()); +} + export default { navigationRef, routeNameRef, navigate, back, - replace + replace, + popToTop }; diff --git a/app/stacks/InsideStack.tsx b/app/stacks/InsideStack.tsx index 95fdac203..d5938cb58 100644 --- a/app/stacks/InsideStack.tsx +++ b/app/stacks/InsideStack.tsx @@ -95,7 +95,7 @@ const ChatsStackNavigator = () => { - + { - + { - const { theme } = this.props; - - return ( - this.removeUser(item)} - testID={`create-channel-view-item-${item.name}`} - icon='check' - theme={theme} - /> - ); - }; + renderItem = ({ item }: { item: IOtherUser }) => ( + this.removeUser(item)} + testID={`create-channel-view-item-${item.name}`} + icon='check' + /> + ); renderInvitedList = () => { const { users, theme } = this.props; diff --git a/app/views/NewMessageView.tsx b/app/views/NewMessageView.tsx index c8f70ec93..2b6f65eb6 100644 --- a/app/views/NewMessageView.tsx +++ b/app/views/NewMessageView.tsx @@ -290,7 +290,6 @@ class NewMessageView extends React.Component this.goRoom(itemModel)} testID={`new-message-view-item-${item.name}`} style={style} - theme={theme} /> ); }; diff --git a/app/views/RoomActionsView/index.tsx b/app/views/RoomActionsView/index.tsx index df0707a70..f440de18b 100644 --- a/app/views/RoomActionsView/index.tsx +++ b/app/views/RoomActionsView/index.tsx @@ -64,10 +64,6 @@ interface IRoomActionsViewProps extends IActionSheetProvider, IBaseScreen { - const { room, joined } = this.state; - const { addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission } = this.props; - const { rid, t } = room; - let canAddUser = false; - - const userInRoom = joined; - const permissions = await hasPermission( - [addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission], - rid - ); - - if (userInRoom && permissions[0]) { - canAddUser = true; - } - if (t === 'c' && permissions[1]) { - canAddUser = true; - } - if (t === 'p' && permissions[2]) { - canAddUser = true; - } - return canAddUser; - }; - - canInviteUser = async () => { - const { room } = this.state; - const { createInviteLinksPermission } = this.props; - const { rid } = room; - const permissions = await hasPermission([createInviteLinksPermission], rid); - - const canInviteUser = permissions[0]; - return canInviteUser; - }; - canEdit = async () => { const { room } = this.state; const { editRoomPermission } = this.props; @@ -1135,7 +1089,7 @@ class RoomActionsView extends React.Component 0 ? `${membersCount} ${I18n.t('members')}` : undefined} - onPress={() => this.onPressTouchable({ route: 'RoomMembersView', params: { rid, room } })} + onPress={() => this.onPressTouchable({ route: 'RoomMembersView', params: { rid, room, joined: this.joined } })} testID='room-actions-members' left={() => } showActionIndicator @@ -1164,45 +1118,6 @@ class RoomActionsView extends React.Component ) : null} - {['c', 'p'].includes(t) && canAddUser ? ( - <> - - this.onPressTouchable({ - route: 'SelectedUsersView', - params: { - title: I18n.t('Add_users'), - nextAction: this.addUser - } - }) - } - testID='room-actions-add-user' - left={() => } - showActionIndicator - /> - - - ) : null} - - {['c', 'p'].includes(t) && canInviteUser ? ( - <> - - this.onPressTouchable({ - route: 'InviteUsersView', - params: { rid } - }) - } - testID='room-actions-invite-user' - left={() => } - showActionIndicator - /> - - - ) : null} - {['c', 'p', 'd'].includes(t) && !prid ? ( <> ({ 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'], - createInviteLinksPermission: state.permissions['create-invite-links'], editRoomPermission: state.permissions['edit-room'], toggleRoomE2EEncryptionPermission: state.permissions['toggle-room-e2e-encryption'], viewBroadcastMemberListPermission: state.permissions['view-broadcast-member-list'], diff --git a/app/views/RoomMembersView/components/ActionsSection.tsx b/app/views/RoomMembersView/components/ActionsSection.tsx new file mode 100644 index 000000000..6442db129 --- /dev/null +++ b/app/views/RoomMembersView/components/ActionsSection.tsx @@ -0,0 +1,109 @@ +import { CompositeNavigationProp, useNavigation } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import React from 'react'; +import { View } from 'react-native'; +import { useDispatch } from 'react-redux'; + +import { setLoading } from '../../../actions/selectedUsers'; +import * as List from '../../../containers/List'; +import { TSubscriptionModel } from '../../../definitions'; +import i18n from '../../../i18n'; +import { usePermissions } from '../../../lib/hooks'; +import log, { events, logEvent } from '../../../lib/methods/helpers/log'; +import { Services } from '../../../lib/services'; +import { MasterDetailInsideStackParamList } from '../../../stacks/MasterDetailStack/types'; +import { ChatsStackParamList } from '../../../stacks/types'; + +type TNavigation = CompositeNavigationProp< + StackNavigationProp, + StackNavigationProp +>; + +interface IActionsSection { + rid: TSubscriptionModel['rid']; + t: TSubscriptionModel['t']; + joined: boolean; +} + +export default function ActionsSection({ rid, t, joined }: IActionsSection): React.ReactElement { + const { navigate, pop } = useNavigation(); + const dispatch = useDispatch(); + const [addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission, createInviteLinksPermission] = + usePermissions(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room', 'create-invite-links'], rid); + + const canAddUser = + (joined && addUserToJoinedRoomPermission) || + (t === 'c' && addUserToAnyCRoomPermission) || + (t === 'p' && addUserToAnyPRoomPermission) || + false; + + const canInviteUser = createInviteLinksPermission; + + const handleOnPress = ({ + route, + params + }: { + route: keyof ChatsStackParamList; + params: ChatsStackParamList[keyof ChatsStackParamList]; + }) => { + navigate(route, params); + // @ts-ignore + logEvent(events[`RM_GO_${route.replace('View', '').toUpperCase()}`]); + }; + + const addUser = async () => { + try { + dispatch(setLoading(true)); + await Services.addUsersToRoom(rid); + pop(); + } catch (e) { + log(e); + } finally { + dispatch(setLoading(false)); + } + }; + + return ( + + {['c', 'p'].includes(t) && canAddUser ? ( + <> + + + handleOnPress({ + route: 'SelectedUsersView', + params: { + title: i18n.t('Add_users'), + nextAction: addUser + } + }) + } + testID='room-actions-add-user' + left={() => } + showActionIndicator + /> + + + ) : null} + + {['c', 'p'].includes(t) && canInviteUser ? ( + <> + + handleOnPress({ + route: 'InviteUsersView', + params: { rid } + }) + } + testID='room-actions-invite-user' + left={() => } + showActionIndicator + /> + + + ) : null} + + ); +} diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts new file mode 100644 index 000000000..f7576fb3e --- /dev/null +++ b/app/views/RoomMembersView/helpers.ts @@ -0,0 +1,258 @@ +import { Q } from '@nozbe/watermelondb'; + +import { LISTENER } from '../../containers/Toast'; +import { IUser, SubscriptionType, TSubscriptionModel, TUserModel } from '../../definitions'; +import I18n from '../../i18n'; +import { getRoomTitle, showConfirmationAlert, showErrorAlert } from '../../lib/methods/helpers'; +import EventEmitter from '../../lib/methods/helpers/events'; +import { goRoom, TGoRoomItem } from '../../lib/methods/helpers/goRoom'; +import log from '../../lib/methods/helpers/log'; +import appNavigation from '../../lib/navigation/appNavigation'; +import { Services } from '../../lib/services'; +import database from '../../lib/database'; +import { RoomTypes } from '../../lib/methods'; + +export type TRoomType = SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL; + +const handleGoRoom = (item: TGoRoomItem, isMasterDetail: boolean): void => { + if (isMasterDetail) { + appNavigation.navigate('DrawerNavigator'); + } else { + appNavigation.popToTop(); + } + goRoom({ item, isMasterDetail }); +}; + +export const fetchRole = (role: string, selectedUser: TUserModel, roomRoles: any): boolean => { + const userRoleResult = roomRoles.find((r: any) => r.u._id === selectedUser._id); + return userRoleResult?.roles.includes(role); +}; + +export const fetchRoomMembersRoles = async (roomType: TRoomType, rid: string, updateState: any): Promise => { + try { + const type = roomType; + const result = await Services.getRoomRoles(rid, type); + if (result?.success) { + updateState({ roomRoles: result.roles }); + } + } catch (e) { + log(e); + } +}; + +export const handleMute = async (user: TUserModel, rid: string) => { + try { + await Services.toggleMuteUserInRoom(rid, user?.username, !user?.muted); + EventEmitter.emit(LISTENER, { + message: I18n.t('User_has_been_key', { key: user?.muted ? I18n.t('unmuted') : I18n.t('muted') }) + }); + } catch (e) { + log(e); + } +}; + +export const handleModerator = async ( + selectedUser: TUserModel, + isModerator: boolean, + room: TSubscriptionModel, + username: string, + callback: () => Promise +): Promise => { + try { + await Services.toggleRoomModerator({ + roomId: room.rid, + t: room.t, + userId: selectedUser._id, + isModerator + }); + const message = isModerator + ? 'User__username__is_now_a_moderator_of__room_name_' + : 'User__username__removed_from__room_name__moderators'; + EventEmitter.emit(LISTENER, { + message: I18n.t(message, { + username, + room_name: getRoomTitle(room) + }) + }); + callback(); + } catch (e) { + log(e); + } +}; + +export const navToDirectMessage = async (item: IUser, isMasterDetail: boolean): Promise => { + try { + const db = database.active; + const subsCollection = db.get('subscriptions'); + const query = await subsCollection.query(Q.where('name', item.username)).fetch(); + if (query.length) { + const [room] = query; + handleGoRoom(room, isMasterDetail); + } else { + const result = await Services.createDirectMessage(item.username); + if (result.success) { + handleGoRoom({ rid: result.room?._id as string, name: item.username, t: SubscriptionType.DIRECT }, isMasterDetail); + } + } + } catch (e) { + log(e); + } +}; + +const removeFromTeam = async ( + selectedUser: IUser, + updateState: Function, + room: TSubscriptionModel, + members: TUserModel[], + selected?: any +) => { + try { + const userId = selectedUser._id; + const result = await Services.removeTeamMember({ + teamId: room.teamId, + userId, + ...(selected && { rooms: selected }) + }); + if (result.success) { + const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) }); + EventEmitter.emit(LISTENER, { message }); + const newMembers = members.filter(member => member._id !== userId); + updateState({ + members: newMembers + }); + } + } catch (e: any) { + log(e); + showErrorAlert( + e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }), + I18n.t('Cannot_remove') + ); + } +}; + +export const handleRemoveFromTeam = async ( + selectedUser: TUserModel, + updateState: Function, + room: TSubscriptionModel, + members: TUserModel[] +): Promise => { + try { + const result = await Services.teamListRoomsOfUser({ teamId: room.teamId as string, userId: selectedUser._id }); + + if (result.success) { + if (result.rooms?.length) { + const teamChannels = result.rooms.map((r: any) => ({ + rid: r._id, + name: r.name, + teamId: r.teamId, + alert: r.isLastOwner + })); + appNavigation.navigate('SelectListView', { + title: 'Remove_Member', + infoText: 'Remove_User_Team_Channels', + data: teamChannels, + nextAction: (selected: any) => removeFromTeam(selectedUser, updateState, room, members, selected), + showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_remove')) + }); + } else { + showConfirmationAlert({ + message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }), + confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }), + onPress: () => removeFromTeam(selectedUser, updateState, room, members) + }); + } + } + } catch (e) { + showConfirmationAlert({ + message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }), + confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }), + onPress: () => removeFromTeam(selectedUser, updateState, room, members) + }); + } +}; + +export const handleLeader = async ( + selectedUser: TUserModel, + isLeader: boolean, + room: TSubscriptionModel, + username: string, + callback: () => Promise +): Promise => { + try { + await Services.toggleRoomLeader({ + roomId: room.rid, + t: room.t, + userId: selectedUser._id, + isLeader + }); + const message = isLeader + ? 'User__username__is_now_a_leader_of__room_name_' + : 'User__username__removed_from__room_name__leaders'; + EventEmitter.emit(LISTENER, { + message: I18n.t(message, { + username, + room_name: getRoomTitle(room) + }) + }); + callback(); + } catch (e) { + log(e); + } +}; + +export const handleRemoveUserFromRoom = async ( + selectedUser: TUserModel, + room: TSubscriptionModel, + callback: Function +): Promise => { + try { + const userId = selectedUser._id; + await Services.removeUserFromRoom({ roomId: room.rid, t: room.t as RoomTypes, userId }); + const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) }); + EventEmitter.emit(LISTENER, { message }); + callback(); + } catch (e) { + log(e); + } +}; + +export const handleIgnore = async (selectedUser: TUserModel, ignore: boolean, rid: string) => { + try { + await Services.ignoreUser({ + rid, + userId: selectedUser._id, + ignore + }); + const message = I18n.t(ignore ? 'User_has_been_ignored' : 'User_has_been_unignored'); + EventEmitter.emit(LISTENER, { message }); + } catch (e) { + log(e); + } +}; + +export const handleOwner = async ( + selectedUser: TUserModel, + isOwner: boolean, + username: string, + room: TSubscriptionModel, + callback: Function +): Promise => { + try { + await Services.toggleRoomOwner({ + roomId: room.rid, + t: room.t, + userId: selectedUser._id, + isOwner + }); + const message = isOwner ? 'User__username__is_now_a_owner_of__room_name_' : 'User__username__removed_from__room_name__owners'; + EventEmitter.emit(LISTENER, { + message: I18n.t(message, { + username, + room_name: getRoomTitle(room) + }) + }); + } catch (e) { + log(e); + } + callback(); +}; diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 2805d8ae8..be7e70d9f 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -1,311 +1,202 @@ -import { Q } from '@nozbe/watermelondb'; -import React from 'react'; -import { FlatList } from 'react-native'; -import { connect } from 'react-redux'; -import { Observable, Subscription } from 'rxjs'; +import { NavigationProp, RouteProp, useNavigation, useRoute } from '@react-navigation/native'; +import React, { useEffect, useReducer } from 'react'; +import { FlatList, Text, View } from 'react-native'; -import { themes } from '../../lib/constants'; -import { TActionSheetOptions, TActionSheetOptionsItem, withActionSheet } from '../../containers/ActionSheet'; +import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet'; import ActivityIndicator from '../../containers/ActivityIndicator'; +import { CustomIcon } from '../../containers/CustomIcon'; import * as HeaderButton from '../../containers/HeaderButton'; import * as List from '../../containers/List'; +import { RadioButton } from '../../containers/RadioButton'; import SafeAreaView from '../../containers/SafeAreaView'; import SearchBox from '../../containers/SearchBox'; import StatusBar from '../../containers/StatusBar'; -import { LISTENER } from '../../containers/Toast'; -import { IApplicationState, IBaseScreen, IUser, SubscriptionType, TSubscriptionModel, TUserModel } from '../../definitions'; -import I18n from '../../i18n'; -import database from '../../lib/database'; -import { CustomIcon } from '../../containers/CustomIcon'; import UserItem from '../../containers/UserItem'; -import { getUserSelector } from '../../selectors/login'; -import { ModalStackParamList } from '../../stacks/MasterDetailStack/types'; -import { TSupportedThemes, withTheme } from '../../theme'; -import EventEmitter from '../../lib/methods/helpers/events'; -import { goRoom, TGoRoomItem } from '../../lib/methods/helpers/goRoom'; -import { showConfirmationAlert, showErrorAlert } from '../../lib/methods/helpers/info'; +import { TSubscriptionModel, TUserModel } from '../../definitions'; +import I18n from '../../i18n'; +import { useAppSelector, usePermissions } from '../../lib/hooks'; +import { getRoomTitle, isGroupChat } from '../../lib/methods/helpers'; +import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import log from '../../lib/methods/helpers/log'; import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps'; -import { TSupportedPermissions } from '../../reducers/permissions'; -import { RoomTypes } from '../../lib/methods'; -import { compareServerVersion, debounce, getRoomTitle, hasPermission, isGroupChat } from '../../lib/methods/helpers'; -import styles from './styles'; import { Services } from '../../lib/services'; +import { TSupportedPermissions } from '../../reducers/permissions'; +import { getUserSelector } from '../../selectors/login'; +import { ModalStackParamList } from '../../stacks/MasterDetailStack/types'; +import { useTheme } from '../../theme'; +import ActionsSection from './components/ActionsSection'; +import { + fetchRole, + fetchRoomMembersRoles, + handleIgnore, + handleLeader, + handleModerator, + handleMute, + handleOwner, + handleRemoveFromTeam, + handleRemoveUserFromRoom, + navToDirectMessage, + TRoomType +} from './helpers'; +import styles from './styles'; const PAGE_SIZE = 25; -interface IRoomMembersViewProps extends IBaseScreen { - rid: string; - members: string[]; - baseUrl: string; - room: TSubscriptionModel; - user: { - id: string; - token: string; - roles: string[]; - }; - showActionSheet: (params: TActionSheetOptions) => {}; - theme: TSupportedThemes; - isMasterDetail: boolean; - useRealName: boolean; - muteUserPermission: string[]; - setLeaderPermission: string[]; - setOwnerPermission: string[]; - setModeratorPermission: string[]; - removeUserPermission: string[]; - editTeamMemberPermission: string[]; - viewAllTeamChannelsPermission: string[]; - viewAllTeamsPermission: string[]; - serverVersion: string; -} - interface IRoomMembersViewState { isLoading: boolean; allUsers: boolean; filtering: string; - rid: string; members: TUserModel[]; - membersFiltered: TUserModel[]; room: TSubscriptionModel; end: boolean; + roomRoles: any; + filter: string; page: number; } -class RoomMembersView extends React.Component { - private mounted: boolean; - private permissions: { [key in TSupportedPermissions]?: boolean }; - private roomObservable!: Observable; - private subscription!: Subscription; - private roomRoles: any; +const RightIcon = ({ check, label }: { check: boolean; label: string }) => { + const { colors } = useTheme(); + return ( + + ); +}; - constructor(props: IRoomMembersViewProps) { - super(props); - this.mounted = false; - this.permissions = {}; - const rid = props.route.params?.rid; - const room = props.route.params?.room; - this.state = { +const RoomMembersView = (): React.ReactElement => { + const { showActionSheet } = useActionSheet(); + const { colors } = useTheme(); + + const { params } = useRoute>(); + const navigation = useNavigation>(); + + const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); + + const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name); + const user = useAppSelector(state => getUserSelector(state)); + + const [state, updateState] = useReducer( + (state: IRoomMembersViewState, newState: Partial) => ({ ...state, ...newState }), + { isLoading: false, allUsers: false, filtering: '', - rid, members: [], - membersFiltered: [], - room: room || ({} as TSubscriptionModel), + room: params.room || ({} as TSubscriptionModel), end: false, + roomRoles: null, + filter: '', page: 0 + } + ); + + const teamPermissions: TSupportedPermissions[] = state.room.teamMain + ? ['edit-team-member', 'view-all-team-channels', 'view-all-teams'] + : []; + + const [ + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + editTeamMemberPermission, + viewAllTeamChannelsPermission, + viewAllTeamsPermission + ] = usePermissions(['mute-user', 'set-leader', 'set-owner', 'set-moderator', 'remove-user', ...teamPermissions], params.rid); + + useEffect(() => { + const subscription = params?.room?.observe && params.room.observe().subscribe(changes => updateState({ room: changes })); + setHeader(true); + fetchMembers(true); + return () => subscription?.unsubscribe(); + }, []); + + useEffect(() => { + const fetchRoles = () => { + if (isGroupChat(state.room)) { + return; + } + if ( + muteUserPermission || + setLeaderPermission || + setOwnerPermission || + setModeratorPermission || + removeUserPermission || + editTeamMemberPermission || + viewAllTeamChannelsPermission || + viewAllTeamsPermission + ) { + fetchRoomMembersRoles(state.room.t as any, state.room.rid, updateState); + } }; - if (room && room.observe) { - this.roomObservable = room.observe(); - this.subscription = this.roomObservable.subscribe(changes => { - if (this.mounted) { - this.setState({ room: changes }); - } else { - this.setState({ room: changes }); - } - }); + fetchRoles(); + }, [ + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + editTeamMemberPermission, + viewAllTeamChannelsPermission, + viewAllTeamsPermission + ]); + + const toggleStatus = (status: boolean) => { + try { + updateState({ members: [], allUsers: status, end: false }); + fetchMembers(status); + setHeader(status); + } catch (e) { + log(e); } - this.setHeader(); - } + }; - async componentDidMount() { - const { room } = this.state; - this.mounted = true; - this.fetchMembers(); - - if (isGroupChat(room)) { - return; - } - - const { - muteUserPermission, - setLeaderPermission, - setOwnerPermission, - setModeratorPermission, - removeUserPermission, - editTeamMemberPermission, - viewAllTeamChannelsPermission, - viewAllTeamsPermission - } = this.props; - - const result = await hasPermission( - [ - muteUserPermission, - setLeaderPermission, - setOwnerPermission, - setModeratorPermission, - removeUserPermission, - ...(room.teamMain ? [editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission] : []) - ], - room.rid - ); - - this.permissions = { - 'mute-user': result[0], - 'set-leader': result[1], - 'set-owner': result[2], - 'set-moderator': result[3], - 'remove-user': result[4], - ...(room.teamMain - ? { - 'edit-team-member': result[5], - 'view-all-team-channels': result[6], - 'view-all-teams': result[7] - } - : {}) - }; - - const hasSinglePermission = Object.values(this.permissions).some(p => !!p); - if (hasSinglePermission) { - this.fetchRoomMembersRoles(); - } - } - - componentWillUnmount() { - if (this.subscription && this.subscription.unsubscribe) { - this.subscription.unsubscribe(); - } - } - - setHeader = () => { - const { allUsers } = this.state; - const { navigation } = this.props; - const toggleText = allUsers ? I18n.t('Online') : I18n.t('All'); + const setHeader = (allUsers: boolean) => { navigation.setOptions({ title: I18n.t('Members'), headerRight: () => ( - + + showActionSheet({ + options: [ + { + title: I18n.t('Online'), + onPress: () => toggleStatus(true), + right: () => , + testID: 'room-members-view-toggle-status-online' + }, + { + title: I18n.t('All'), + onPress: () => toggleStatus(false), + right: () => , + testID: 'room-members-view-toggle-status-all' + } + ] + }) + } + testID='room-members-view-filter' + /> ) }); }; - get isServerVersionLowerThan3_16() { - const { serverVersion } = this.props; - return compareServerVersion(serverVersion, 'lowerThan', '3.16.0'); - } + const getUserDisplayName = (user: TUserModel) => (useRealName ? user.name : user.username) || user.username; - onSearchChangeText = debounce((text: string) => { - const { members } = this.state; - text = text.trim(); - if (this.isServerVersionLowerThan3_16) { - let membersFiltered: TUserModel[] = []; - - if (members && members.length > 0 && text) { - membersFiltered = members.filter( - m => m.username.toLowerCase().match(text.toLowerCase()) || m.name?.toLowerCase().match(text.toLowerCase()) - ); - } - return this.setState({ filtering: text, membersFiltered }); - } - - this.setState({ filtering: text, page: 0, members: [], end: false }, () => { - this.fetchMembers(); - }); - }, 500); - - navToDirectMessage = async (item: IUser) => { - try { - const db = database.active; - const subsCollection = db.get('subscriptions'); - const query = await subsCollection.query(Q.where('name', item.username)).fetch(); - if (query.length) { - const [room] = query; - this.goRoom(room); - } else { - const result = await Services.createDirectMessage(item.username); - if (result.success) { - this.goRoom({ rid: result.room?._id as string, name: item.username, t: SubscriptionType.DIRECT }); - } - } - } catch (e) { - log(e); - } - }; - - handleRemoveFromTeam = async (selectedUser: TUserModel) => { - try { - const { navigation } = this.props; - const { room } = this.state; - - const result = await Services.teamListRoomsOfUser({ teamId: room.teamId as string, userId: selectedUser._id }); - - if (result.success) { - if (result.rooms?.length) { - const teamChannels = result.rooms.map((r: any) => ({ - rid: r._id, - name: r.name, - teamId: r.teamId, - alert: r.isLastOwner - })); - navigation.navigate('SelectListView', { - title: 'Remove_Member', - infoText: 'Remove_User_Team_Channels', - data: teamChannels, - nextAction: (selected: any) => this.removeFromTeam(selectedUser, selected), - showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_remove')) - }); - } else { - showConfirmationAlert({ - message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }), - confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }), - onPress: () => this.removeFromTeam(selectedUser) - }); - } - } - } catch (e) { - showConfirmationAlert({ - message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }), - confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }), - onPress: () => this.removeFromTeam(selectedUser) - }); - } - }; - - removeFromTeam = async (selectedUser: IUser, selected?: any) => { - try { - const { members, membersFiltered, room } = this.state; - const { navigation } = this.props; - - const userId = selectedUser._id; - const result = await Services.removeTeamMember({ - teamId: room.teamId, - userId, - ...(selected && { rooms: selected }) - }); - if (result.success) { - const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) }); - EventEmitter.emit(LISTENER, { message }); - const newMembers = members.filter(member => member._id !== userId); - const newMembersFiltered = this.isServerVersionLowerThan3_16 - ? membersFiltered.filter(member => member._id !== userId) - : []; - this.setState({ - members: newMembers, - membersFiltered: newMembersFiltered - }); - // @ts-ignore - This is just to force a reload - navigation.navigate('RoomMembersView'); - } - } catch (e: any) { - log(e); - showErrorAlert( - e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }), - I18n.t('Cannot_remove') - ); - } - }; - - onPressUser = (selectedUser: TUserModel) => { - const { room } = this.state; - const { showActionSheet, user, theme } = this.props; + const onPressUser = (selectedUser: TUserModel) => { + const { room, roomRoles, members } = state; const options: TActionSheetOptionsItem[] = [ { icon: 'message', title: I18n.t('Direct_message'), - onPress: () => this.navToDirectMessage(selectedUser) + onPress: () => navToDirectMessage(selectedUser, isMasterDetail) } ]; @@ -316,12 +207,12 @@ class RoomMembersView extends React.Component this.handleIgnore(selectedUser, !isIgnored), + onPress: () => handleIgnore(selectedUser, !isIgnored, room.rid), testID: 'action-sheet-ignore-user' }); } - if (this.permissions['mute-user']) { + if (muteUserPermission) { const { muted = [] } = room; const userIsMuted = muted.find?.(m => m === selectedUser.username); selectedUser.muted = !!userIsMuted; @@ -334,7 +225,7 @@ class RoomMembersView extends React.Component this.handleMute(selectedUser) + onPress: () => handleMute(selectedUser, room.rid) }); }, testID: 'action-sheet-mute-user' @@ -342,78 +233,63 @@ class RoomMembersView extends React.Component r.u._id === selectedUser._id); - const isOwner = userRoleResult?.roles.includes('owner'); + if (setOwnerPermission) { + const isOwner = fetchRole('owner', selectedUser, roomRoles); options.push({ icon: 'shield-check', title: I18n.t('Owner'), - onPress: () => this.handleOwner(selectedUser, !isOwner), - right: () => ( - - ), + onPress: () => + handleOwner(selectedUser, !isOwner, getUserDisplayName(selectedUser), room, () => + fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + ), + right: () => , testID: 'action-sheet-set-owner' }); } // Leader - if (this.permissions['set-leader']) { - const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id); - const isLeader = userRoleResult?.roles.includes('leader'); + if (setLeaderPermission) { + const isLeader = fetchRole('leader', selectedUser, roomRoles); options.push({ icon: 'shield-alt', title: I18n.t('Leader'), - onPress: () => this.handleLeader(selectedUser, !isLeader), - right: () => ( - - ), + onPress: () => + handleLeader(selectedUser, !isLeader, room, getUserDisplayName(selectedUser), () => + fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + ), + right: () => , testID: 'action-sheet-set-leader' }); } // Moderator - if (this.permissions['set-moderator']) { - const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id); - const isModerator = userRoleResult?.roles.includes('moderator'); + if (setModeratorPermission) { + const isModerator = fetchRole('moderator', selectedUser, roomRoles); options.push({ icon: 'shield', title: I18n.t('Moderator'), - onPress: () => this.handleModerator(selectedUser, !isModerator), - right: () => ( - - ), + onPress: () => + handleModerator(selectedUser, !isModerator, room, getUserDisplayName(selectedUser), () => + fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + ), + right: () => , testID: 'action-sheet-set-moderator' }); } // Remove from team - if (this.permissions['edit-team-member']) { + if (editTeamMemberPermission) { options.push({ icon: 'logout', danger: true, title: I18n.t('Remove_from_Team'), - onPress: () => this.handleRemoveFromTeam(selectedUser), + onPress: () => handleRemoveFromTeam(selectedUser, updateState, room, members), testID: 'action-sheet-remove-from-team' }); } // Remove from room - if (this.permissions['remove-user'] && !room.teamMain) { + if (removeUserPermission && !room.teamMain) { options.push({ icon: 'logout', title: I18n.t('Remove_from_room'), @@ -422,7 +298,13 @@ class RoomMembersView extends React.Component this.handleRemoveUserFromRoom(selectedUser) + onPress: () => { + handleRemoveUserFromRoom(selectedUser, room, () => + updateState({ + members: members.filter(member => member._id !== selectedUser._id) + }) + ); + } }); }, testID: 'action-sheet-remove-from-room' @@ -435,256 +317,83 @@ class RoomMembersView extends React.Component { - try { - const { allUsers } = this.state; - this.setState({ members: [], allUsers: !allUsers, end: false, page: 0 }, () => { - this.fetchMembers(); - }); - } catch (e) { - log(e); - } - }; - - fetchRoomMembersRoles = async () => { - try { - const { room } = this.state; - const type = room.t as SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL; - const result = await Services.getRoomRoles(room.rid, type); - if (result?.success) { - this.roomRoles = result.roles; - } - } catch (e) { - log(e); - } - }; - - fetchMembers = async () => { - const { rid, members, isLoading, allUsers, end, room, filtering, page } = this.state; + const fetchMembers = async (status: boolean) => { + const { members, isLoading, end, room, filter, page } = state; const { t } = room; if (isLoading || end) { return; } - this.setState({ isLoading: true }); + updateState({ isLoading: true }); try { const membersResult = await Services.getRoomMembers({ - rid, + rid: room.rid, roomType: t, - type: allUsers ? 'all' : 'online', - filter: filtering, + type: !status ? 'all' : 'online', + filter, skip: PAGE_SIZE * page, limit: PAGE_SIZE, - allUsers + allUsers: !status }); const end = membersResult?.length < PAGE_SIZE; const membersResultFiltered = membersResult?.filter((member: TUserModel) => !members.some(m => m._id === member._id)); - this.setState({ - members: members.concat(membersResultFiltered || []), + updateState({ + members: [...members, ...membersResultFiltered], isLoading: false, end, page: page + 1 }); - this.setHeader(); } catch (e) { log(e); - this.setState({ isLoading: false }); + updateState({ isLoading: false }); } }; - goRoom = (item: TGoRoomItem) => { - const { navigation, isMasterDetail } = this.props; - if (isMasterDetail) { - // @ts-ignore - navigation.navigate('DrawerNavigator'); - } else { - navigation.popToTop(); - } - goRoom({ item, isMasterDetail }); - }; + const filteredMembers = + state.members && state.members.length > 0 && state.filter + ? state.members.filter( + m => + m.username.toLowerCase().match(state.filter.toLowerCase()) || m.name?.toLowerCase().match(state.filter.toLowerCase()) + ) + : null; - getUserDisplayName = (user: TUserModel) => { - const { useRealName } = this.props; - return (useRealName ? user.name : user.username) || user.username; - }; - - handleMute = async (user: TUserModel) => { - const { rid } = this.state; - try { - await Services.toggleMuteUserInRoom(rid, user?.username, !user?.muted); - EventEmitter.emit(LISTENER, { - message: I18n.t('User_has_been_key', { key: user?.muted ? I18n.t('unmuted') : I18n.t('muted') }) - }); - } catch (e) { - log(e); - } - }; - - handleOwner = async (selectedUser: TUserModel, isOwner: boolean) => { - try { - const { room } = this.state; - await Services.toggleRoomOwner({ - roomId: room.rid, - t: room.t, - userId: selectedUser._id, - isOwner - }); - const message = isOwner - ? 'User__username__is_now_a_owner_of__room_name_' - : 'User__username__removed_from__room_name__owners'; - EventEmitter.emit(LISTENER, { - message: I18n.t(message, { - username: this.getUserDisplayName(selectedUser), - room_name: getRoomTitle(room) - }) - }); - } catch (e) { - log(e); - } - this.fetchRoomMembersRoles(); - }; - - handleLeader = async (selectedUser: TUserModel, isLeader: boolean) => { - try { - const { room } = this.state; - await Services.toggleRoomLeader({ - roomId: room.rid, - t: room.t, - userId: selectedUser._id, - isLeader - }); - const message = isLeader - ? 'User__username__is_now_a_leader_of__room_name_' - : 'User__username__removed_from__room_name__leaders'; - EventEmitter.emit(LISTENER, { - message: I18n.t(message, { - username: this.getUserDisplayName(selectedUser), - room_name: getRoomTitle(room) - }) - }); - } catch (e) { - log(e); - } - this.fetchRoomMembersRoles(); - }; - - handleModerator = async (selectedUser: TUserModel, isModerator: boolean) => { - try { - const { room } = this.state; - await Services.toggleRoomModerator({ - roomId: room.rid, - t: room.t, - userId: selectedUser._id, - isModerator - }); - const message = isModerator - ? 'User__username__is_now_a_moderator_of__room_name_' - : 'User__username__removed_from__room_name__moderators'; - EventEmitter.emit(LISTENER, { - message: I18n.t(message, { - username: this.getUserDisplayName(selectedUser), - room_name: getRoomTitle(room) - }) - }); - } catch (e) { - log(e); - } - this.fetchRoomMembersRoles(); - }; - - handleIgnore = async (selectedUser: TUserModel, ignore: boolean) => { - try { - const { room } = this.state; - await Services.ignoreUser({ - rid: room.rid, - userId: selectedUser._id, - ignore - }); - const message = I18n.t(ignore ? 'User_has_been_ignored' : 'User_has_been_unignored'); - EventEmitter.emit(LISTENER, { message }); - } catch (e) { - log(e); - } - }; - - handleRemoveUserFromRoom = async (selectedUser: TUserModel) => { - try { - const { room, members, membersFiltered } = this.state; - const userId = selectedUser._id; - // TODO: interface SubscriptionType on IRoom is wrong - await Services.removeUserFromRoom({ roomId: room.rid, t: room.t as RoomTypes, userId }); - const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) }); - EventEmitter.emit(LISTENER, { message }); - this.setState({ - members: members.filter(member => member._id !== userId), - membersFiltered: this.isServerVersionLowerThan3_16 ? membersFiltered.filter(member => member._id !== userId) : [] - }); - } catch (e) { - log(e); - } - }; - - renderSearchBar = () => this.onSearchChangeText(text)} testID='room-members-view-search' />; - - renderItem = ({ item }: { item: TUserModel }) => { - const { theme } = this.props; - - return ( - this.onPressUser(item)} - testID={`room-members-view-item-${item.username}`} - theme={theme} + return ( + + + ( + + onPressUser(item)} + testID={`room-members-view-item-${item.username}`} + /> + + )} + style={styles.list} + keyExtractor={item => item._id} + ItemSeparatorComponent={List.Separator} + ListHeaderComponent={ + <> + + + updateState({ filter: text.trim() })} testID='room-members-view-search' /> + + + } + ListFooterComponent={() => (state.isLoading ? : null)} + onEndReachedThreshold={0.1} + onEndReached={() => fetchMembers(state.allUsers)} + ListEmptyComponent={() => + state.end ? {I18n.t('No_members_found')} : null + } + {...scrollPersistTaps} /> - ); - }; + + ); +}; - render() { - const { filtering, members, membersFiltered, isLoading } = this.state; - const { theme } = this.props; - return ( - - - item._id} - ItemSeparatorComponent={List.Separator} - ListHeaderComponent={this.renderSearchBar} - ListFooterComponent={() => { - if (isLoading) { - return ; - } - return null; - }} - onEndReachedThreshold={0.1} - onEndReached={this.fetchMembers} - maxToRenderPerBatch={5} - windowSize={10} - {...scrollPersistTaps} - /> - - ); - } -} - -const mapStateToProps = (state: IApplicationState) => ({ - baseUrl: state.server.server, - user: getUserSelector(state), - isMasterDetail: state.app.isMasterDetail, - useRealName: state.settings.UI_Use_Real_Name, - muteUserPermission: state.permissions['mute-user'], - setLeaderPermission: state.permissions['set-leader'], - setOwnerPermission: state.permissions['set-owner'], - setModeratorPermission: state.permissions['set-moderator'], - removeUserPermission: state.permissions['remove-user'], - editTeamMemberPermission: state.permissions['edit-team-member'], - viewAllTeamChannelsPermission: state.permissions['view-all-team-channels'], - viewAllTeamsPermission: state.permissions['view-all-teams'], - serverVersion: state.server.version -}); - -export default connect(mapStateToProps)(withTheme(withActionSheet(RoomMembersView))); +export default RoomMembersView; diff --git a/app/views/RoomMembersView/styles.ts b/app/views/RoomMembersView/styles.ts index d183ebb09..8531b8172 100644 --- a/app/views/RoomMembersView/styles.ts +++ b/app/views/RoomMembersView/styles.ts @@ -1,20 +1,15 @@ import { StyleSheet } from 'react-native'; +import sharedStyles from '../Styles'; + export default StyleSheet.create({ list: { flex: 1 }, - item: { - flexDirection: 'row', - paddingVertical: 10, - paddingHorizontal: 16, - alignItems: 'center' - }, - avatar: { - marginRight: 16 - }, - separator: { - height: StyleSheet.hairlineWidth, - marginLeft: 60 + noResult: { + fontSize: 16, + paddingVertical: 56, + ...sharedStyles.textSemibold, + ...sharedStyles.textAlignCenter } }); diff --git a/app/views/SelectedUsersView.tsx b/app/views/SelectedUsersView.tsx index 08a720836..a651a7178 100644 --- a/app/views/SelectedUsersView.tsx +++ b/app/views/SelectedUsersView.tsx @@ -211,19 +211,15 @@ class SelectedUsersView extends React.Component { - const { theme } = this.props; - return ( - this._onPressSelectedItem(item)} - testID={`selected-user-${item.name}`} - style={{ paddingRight: 15 }} - theme={theme} - /> - ); - }; + renderSelectedItem = ({ item }: { item: ISelectedUser }) => ( + this._onPressSelectedItem(item)} + testID={`selected-user-${item.name}`} + style={{ paddingRight: 15 }} + /> + ); renderItem = ({ item, index }: { item: ISelectedUser; index: number }) => { const { search, chats } = this.state; @@ -249,7 +245,6 @@ class SelectedUsersView extends React.Component ); }; diff --git a/e2e/tests/room/03-roomactions.spec.js b/e2e/tests/room/03-roomactions.spec.js index 74a0fdbf8..5acb56f0a 100644 --- a/e2e/tests/room/03-roomactions.spec.js +++ b/e2e/tests/room/03-roomactions.spec.js @@ -146,10 +146,6 @@ describe('Room actions screen', () => { await expect(element(by.id('room-actions-members'))).toExist(); }); - it('should have add user', async () => { - await expect(element(by.id('room-actions-add-user'))).toExist(); - }); - it('should have files', async () => { await expect(element(by.id('room-actions-files'))).toExist(); }); @@ -303,24 +299,12 @@ describe('Room actions screen', () => { .withTimeout(4000); }); - it('should have notification audio option', async () => { - await waitFor(element(by.id('notification-preference-view-audio'))) - .toExist() - .withTimeout(4000); - }); - it('should have notification sound option', async () => { await waitFor(element(by.id('notification-preference-view-sound'))) .toExist() .withTimeout(4000); }); - it('should have notification duration option', async () => { - await waitFor(element(by.id('notification-preference-view-notification-duration'))) - .toExist() - .withTimeout(4000); - }); - it('should have email alert option', async () => { await waitFor(element(by.id('notification-preference-view-email-alert'))) .toExist() @@ -361,6 +345,14 @@ describe('Room actions screen', () => { }); it('should add users to the room', async () => { + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-members')).tap(); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id('room-actions-add-user'))) .toExist() .withTimeout(4000); @@ -392,19 +384,14 @@ describe('Room actions screen', () => { await element(by.id('selected-users-view-submit')).tap(); await sleep(300); - await waitFor(element(by.id('room-actions-members'))) - .toExist() - .withTimeout(10000); - await element(by.id('room-actions-members')).tap(); - await element(by.id('room-members-view-toggle-status')).tap(); - await waitFor(element(by.id(`room-members-view-item-${user.username}`))) - .toExist() - .withTimeout(60000); await backToActions(); }); describe('Room Members', () => { before(async () => { + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(2000); await element(by.id('room-actions-members')).tap(); await waitFor(element(by.id('room-members-view'))) .toExist() @@ -442,13 +429,30 @@ describe('Room actions screen', () => { }; it('should show all users', async () => { - await element(by.id('room-members-view-toggle-status')).tap(); + await waitFor(element(by.id('room-members-view-filter'))) + .toExist() + .withTimeout(10000); + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); await waitFor(element(by.id(`room-members-view-item-${user.username}`))) .toExist() .withTimeout(60000); + await tapBack(); }); it('should filter user', async () => { + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-actions-members')).tap(); + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); await waitFor(element(by.id(`room-members-view-item-${user.username}`))) .toExist() .withTimeout(60000); @@ -595,11 +599,21 @@ describe('Room actions screen', () => { await waitFor(element(by.id('room-actions-view'))) .toExist() .withTimeout(5000); + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(2000); await element(by.id('room-actions-members')).tap(); await waitFor(element(by.id('room-members-view'))) .toExist() .withTimeout(2000); - await element(by.id('room-members-view-toggle-status')).tap(); + await waitFor(element(by.id('room-members-view-filter'))) + .toExist() + .withTimeout(10000); + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); await waitFor(element(by.id(`room-members-view-item-${user.username}`))) .toExist() .withTimeout(60000); @@ -625,6 +639,7 @@ describe('Room actions screen', () => { }); it('should block/unblock user', async () => { + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); await waitFor(element(by.id('room-actions-block-user'))).toExist(); await element(by.id('room-actions-block-user')).tap(); await waitFor(element(by[textMatcher]('Unblock user'))) diff --git a/e2e/tests/team/02-team.spec.js b/e2e/tests/team/02-team.spec.js index 6994703cb..47a3213da 100644 --- a/e2e/tests/team/02-team.spec.js +++ b/e2e/tests/team/02-team.spec.js @@ -266,6 +266,11 @@ describe('Team', () => { }); it('should add users to the team', async () => { + await element(by.id('room-actions-members')).tap(); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id('room-actions-add-user'))) .toExist() .withTimeout(10000); @@ -296,11 +301,17 @@ describe('Team', () => { await element(by.id('selected-users-view-submit')).tap(); await sleep(300); + await tapBack(); + await sleep(300); await waitFor(element(by.id('room-actions-members'))) .toExist() .withTimeout(10000); await element(by.id('room-actions-members')).tap(); - await element(by.id('room-members-view-toggle-status')).tap(); + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); await waitFor(element(by.id(`room-members-view-item-${user.username}`))) .toExist() .withTimeout(60000); @@ -358,7 +369,11 @@ describe('Team', () => { }); it('should show all users', async () => { - await element(by.id('room-members-view-toggle-status')).tap(); + await element(by.id('room-members-view-filter')).tap(); + await waitFor(element(by.id('room-members-view-toggle-status-all'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-members-view-toggle-status-all')).tap(); await waitFor(element(by.id(`room-members-view-item-${user.username}`))) .toExist() .withTimeout(60000);