Rocket.Chat.ReactNative/app/views/RoomMembersView/index.tsx

413 lines
12 KiB
TypeScript

import { NavigationProp, RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import React, { useEffect, useReducer } from 'react';
import { FlatList, Text, View } from 'react-native';
import { shallowEqual } from 'react-redux';
import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet';
import ActivityIndicator from '../../containers/ActivityIndicator';
import { CustomIcon, TIconsName } 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 UserItem from '../../containers/UserItem';
import { TSubscriptionModel, TUserModel } from '../../definitions';
import I18n from '../../i18n';
import { useAppSelector, usePermissions } from '../../lib/hooks';
import { compareServerVersion, getRoomTitle, isGroupChat } from '../../lib/methods/helpers';
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
import log from '../../lib/methods/helpers/log';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
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,
handleLeader,
handleModerator,
handleMute,
handleOwner,
handleRemoveFromTeam,
handleRemoveUserFromRoom,
navToDirectMessage,
TRoomType
} from './helpers';
import styles from './styles';
const PAGE_SIZE = 25;
interface IRoomMembersViewState {
isLoading: boolean;
allUsers: boolean;
filtering: string;
members: TUserModel[];
room: TSubscriptionModel;
end: boolean;
roomRoles: any;
filter: string;
page: number;
}
const RightIcon = ({ check, label }: { check: boolean; label: string }) => {
const { colors } = useTheme();
return (
<CustomIcon
testID={check ? `action-sheet-set-${label}-checked` : `action-sheet-set-${label}-unchecked`}
name={check ? 'checkbox-checked' : 'checkbox-unchecked'}
size={20}
color={check ? colors.tintActive : colors.auxiliaryTintColor}
/>
);
};
const RoomMembersView = (): React.ReactElement => {
const { showActionSheet } = useActionSheet();
const { colors } = useTheme();
const { params } = useRoute<RouteProp<ModalStackParamList, 'RoomMembersView'>>();
const navigation = useNavigation<NavigationProp<ModalStackParamList, 'RoomMembersView'>>();
const { isMasterDetail, serverVersion, useRealName, user } = useAppSelector(
state => ({
isMasterDetail: state.app.isMasterDetail,
useRealName: state.settings.UI_Use_Real_Name,
user: getUserSelector(state),
serverVersion: state.server.version
}),
shallowEqual
);
const [state, updateState] = useReducer(
(state: IRoomMembersViewState, newState: Partial<IRoomMembersViewState>) => ({ ...state, ...newState }),
{
isLoading: false,
allUsers: false,
filtering: '',
members: [],
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(false);
fetchMembers(false);
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);
}
};
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);
}
};
const setHeader = (allUsers: boolean) => {
navigation.setOptions({
title: I18n.t('Members'),
headerRight: () => (
<HeaderButton.Container>
<HeaderButton.Item
iconName='filter'
onPress={() =>
showActionSheet({
options: [
{
title: I18n.t('Online'),
onPress: () => toggleStatus(true),
right: () => <RadioButton check={allUsers} />,
testID: 'room-members-view-toggle-status-online'
},
{
title: I18n.t('All'),
onPress: () => toggleStatus(false),
right: () => <RadioButton check={!allUsers} />,
testID: 'room-members-view-toggle-status-all'
}
]
})
}
testID='room-members-view-filter'
/>
</HeaderButton.Container>
)
});
};
const getUserDisplayName = (user: TUserModel) => (useRealName ? user.name : user.username) || user.username;
const onPressUser = (selectedUser: TUserModel) => {
const { room, roomRoles, members } = state;
const options: TActionSheetOptionsItem[] = [
{
icon: 'message',
title: I18n.t('Direct_message'),
onPress: () => navToDirectMessage(selectedUser, isMasterDetail)
}
];
// Owner
if (setOwnerPermission) {
const isOwner = fetchRole('owner', selectedUser, roomRoles);
options.push({
icon: 'shield-check',
title: I18n.t('Owner'),
onPress: () =>
handleOwner(selectedUser, !isOwner, getUserDisplayName(selectedUser), room, () =>
fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState)
),
right: () => <RightIcon check={isOwner} label='owner' />,
testID: 'action-sheet-set-owner'
});
}
// Leader
if (setLeaderPermission) {
const isLeader = fetchRole('leader', selectedUser, roomRoles);
options.push({
icon: 'shield-alt',
title: I18n.t('Leader'),
onPress: () =>
handleLeader(selectedUser, !isLeader, room, getUserDisplayName(selectedUser), () =>
fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState)
),
right: () => <RightIcon check={isLeader} label='leader' />,
testID: 'action-sheet-set-leader'
});
}
// Moderator
if (setModeratorPermission) {
const isModerator = fetchRole('moderator', selectedUser, roomRoles);
options.push({
icon: 'shield',
title: I18n.t('Moderator'),
onPress: () =>
handleModerator(selectedUser, !isModerator, room, getUserDisplayName(selectedUser), () =>
fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState)
),
right: () => <RightIcon check={isModerator} label='moderator' />,
testID: 'action-sheet-set-moderator'
});
}
if (muteUserPermission) {
const { muted = [], ro: readOnly, unmuted = [] } = room;
let userIsMuted = !!muted.find?.(m => m === selectedUser.username);
let icon: TIconsName = userIsMuted ? 'audio' : 'audio-disabled';
let title = I18n.t(userIsMuted ? 'Unmute' : 'Mute');
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.4.0')) {
if (readOnly) {
userIsMuted = !unmuted?.find?.(m => m === selectedUser.username);
}
icon = userIsMuted ? 'message' : 'message-disabled';
title = I18n.t(userIsMuted ? 'Enable_writing_in_room' : 'Disable_writing_in_room');
}
selectedUser.muted = !!userIsMuted;
options.push({
icon,
title,
onPress: () => {
showConfirmationAlert({
message: I18n.t(`The_user_${userIsMuted ? 'will' : 'wont'}_be_able_to_type_in_roomName`, {
roomName: getRoomTitle(room)
}),
confirmationText: title,
onPress: () => handleMute(selectedUser, room.rid)
});
},
testID: 'action-sheet-mute-user'
});
}
// 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: () => handleIgnore(selectedUser._id, !isIgnored, room.rid),
testID: 'action-sheet-ignore-user'
});
}
// Remove from team
if (editTeamMemberPermission) {
options.push({
icon: 'logout',
danger: true,
title: I18n.t('Remove_from_Team'),
onPress: () => handleRemoveFromTeam(selectedUser, updateState, room, members),
testID: 'action-sheet-remove-from-team'
});
}
// Remove from room
if (removeUserPermission && !room.teamMain) {
options.push({
icon: 'logout',
title: I18n.t('Remove_from_room'),
danger: true,
onPress: () => {
showConfirmationAlert({
message: I18n.t('The_user_will_be_removed_from_s', { s: getRoomTitle(room) }),
confirmationText: I18n.t('Yes_remove_user'),
onPress: () => {
handleRemoveUserFromRoom(selectedUser, room, () =>
updateState({
members: members.filter(member => member._id !== selectedUser._id)
})
);
}
});
},
testID: 'action-sheet-remove-from-room'
});
}
showActionSheet({
options,
hasCancel: true
});
};
const fetchMembers = async (status: boolean) => {
const { members, isLoading, end, room, filter, page } = state;
const { t } = room;
if (isLoading || end) {
return;
}
updateState({ isLoading: true });
try {
const membersResult = await Services.getRoomMembers({
rid: room.rid,
roomType: t,
type: !status ? 'all' : 'online',
filter,
skip: PAGE_SIZE * page,
limit: PAGE_SIZE,
allUsers: !status
});
const end = membersResult?.length < PAGE_SIZE;
const membersResultFiltered = membersResult?.filter((member: TUserModel) => !members.some(m => m._id === member._id));
updateState({
members: [...members, ...membersResultFiltered],
isLoading: false,
end,
page: page + 1
});
} catch (e) {
log(e);
updateState({ isLoading: false });
}
};
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;
return (
<SafeAreaView testID='room-members-view'>
<StatusBar />
<FlatList
data={filteredMembers || state.members}
renderItem={({ item }) => (
<View style={{ backgroundColor: colors.backgroundColor }}>
<UserItem
name={item.name as string}
username={item.username}
onPress={() => onPressUser(item)}
testID={`room-members-view-item-${item.username}`}
/>
</View>
)}
style={styles.list}
keyExtractor={item => item._id}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={
<>
<ActionsSection joined={params.joined as boolean} rid={state.room.rid} t={state.room.t} />
<SearchBox onChangeText={text => updateState({ filter: text.trim() })} testID='room-members-view-search' />
</>
}
ListFooterComponent={() => (state.isLoading ? <ActivityIndicator /> : null)}
onEndReachedThreshold={0.1}
onEndReached={() => fetchMembers(state.allUsers)}
ListEmptyComponent={() =>
state.end ? <Text style={[styles.noResult, { color: colors.titleText }]}>{I18n.t('No_members_found')}</Text> : null
}
{...scrollPersistTaps}
/>
</SafeAreaView>
);
};
export default RoomMembersView;