[IMPROVE] Add ignore user to user profile (#4600)

* button ignore and pased the param

* load room from database and check is is ignored

* move handleIgnore to lib/method/helpers

* Ignore and Unignore, also reactivity

* block and unblock user

* pass fromRid from actionView to InfoView too

* remove console.log

* unsubscribe subscriptionFrom

* block and unblock user from dm

* test to block user and ignore user

* minor tweak

* tweak data

* minor tweak

* add test before tapBack

* refactor names
This commit is contained in:
Reinaldo Neto 2022-10-13 18:29:55 -03:00 committed by GitHub
parent 300e5fc793
commit 531f3d0147
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 220 additions and 38 deletions

View File

@ -0,0 +1,19 @@
import { LISTENER } from '../../../containers/Toast';
import I18n from '../../../i18n';
import EventEmitter from './events';
import log from './log';
import { Services } from '../../services';
export const handleIgnore = async (userId: string, ignore: boolean, rid: string) => {
try {
await Services.ignoreUser({
rid,
userId,
ignore
});
const message = I18n.t(ignore ? 'User_has_been_ignored' : 'User_has_been_unignored');
EventEmitter.emit(LISTENER, { message });
} catch (e) {
log(e);
}
};

View File

@ -280,6 +280,7 @@ export default {
RI_GO_RI_EDIT: 'ri_go_ri_edit', RI_GO_RI_EDIT: 'ri_go_ri_edit',
RI_GO_LIVECHAT_EDIT: 'ri_go_livechat_edit', RI_GO_LIVECHAT_EDIT: 'ri_go_livechat_edit',
RI_GO_ROOM_USER: 'ri_go_room_user', RI_GO_ROOM_USER: 'ri_go_room_user',
RI_TOGGLE_BLOCK_USER: 'ri_toggle_block_user',
// ROOM INFO EDIT VIEW // ROOM INFO EDIT VIEW
RI_EDIT_TOGGLE_ROOM_TYPE: 'ri_edit_toggle_room_type', RI_EDIT_TOGGLE_ROOM_TYPE: 'ri_edit_toggle_room_type',

View File

@ -68,6 +68,7 @@ export type ChatsStackParamList = {
rid: string; rid: string;
t: SubscriptionType; t: SubscriptionType;
showCloseModal?: boolean; showCloseModal?: boolean;
fromRid?: string;
}; };
RoomInfoEditView: { RoomInfoEditView: {
rid: string; rid: string;

View File

@ -760,7 +760,8 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
rid, rid,
t, t,
room, room,
member member,
fromRid: room.rid
} }
}) })
} }

View File

@ -36,6 +36,8 @@ import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
import { callJitsi } from '../../lib/methods'; import { callJitsi } from '../../lib/methods';
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers'; import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription';
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
interface IGetRoomTitle { interface IGetRoomTitle {
room: ISubscription; room: ISubscription;
@ -108,6 +110,7 @@ interface IRoomInfoViewState {
room: ISubscription; room: ISubscription;
roomUser: IUserParsed | ILivechatVisitorModified; roomUser: IUserParsed | ILivechatVisitorModified;
showEdit: boolean; showEdit: boolean;
roomFromRid?: TSubscriptionModel;
} }
class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewState> { class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewState> {
@ -121,22 +124,29 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
private roomObservable?: Observable<TSubscriptionModel>; private roomObservable?: Observable<TSubscriptionModel>;
private fromRid?: string;
private subscriptionRoomFromRid?: Subscription;
constructor(props: IRoomInfoViewProps) { constructor(props: IRoomInfoViewProps) {
super(props); super(props);
const room = props.route.params?.room; const room = props.route.params?.room;
const roomUser = props.route.params?.member; const roomUser = props.route.params?.member;
this.rid = props.route.params?.rid; this.rid = props.route.params?.rid;
this.t = props.route.params?.t; this.t = props.route.params?.t;
this.fromRid = props.route.params?.fromRid;
this.state = { this.state = {
room: (room || { rid: this.rid, t: this.t }) as any, room: (room || { rid: this.rid, t: this.t }) as any,
roomUser: roomUser || {}, roomUser: roomUser || {},
showEdit: false showEdit: false,
roomFromRid: undefined
}; };
} }
componentDidMount() { componentDidMount() {
if (this.isDirect) { if (this.isDirect) {
this.loadUser(); this.loadUser();
this.loadRoomFromRid();
} else { } else {
this.loadRoom(); this.loadRoom();
} }
@ -154,6 +164,9 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
if (this.subscription && this.subscription.unsubscribe) { if (this.subscription && this.subscription.unsubscribe) {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
if (this.subscriptionRoomFromRid && this.subscriptionRoomFromRid.unsubscribe) {
this.subscriptionRoomFromRid.unsubscribe();
}
if (this.unsubscribeFocus) { if (this.unsubscribeFocus) {
this.unsubscribeFocus(); this.unsubscribeFocus();
} }
@ -266,6 +279,19 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
} }
}; };
loadRoomFromRid = async () => {
if (this.fromRid) {
try {
const sub = await getSubscriptionByRoomId(this.fromRid);
this.subscriptionRoomFromRid = sub?.observe().subscribe(roomFromRid => {
this.setState({ roomFromRid });
});
} catch (e) {
// do nothing
}
}
};
loadRoom = async () => { loadRoom = async () => {
const { room: roomState } = this.state; const { room: roomState } = this.state;
const { route, editRoomPermission, editOmnichannelContact, editLivechatRoomCustomfields } = this.props; const { route, editRoomPermission, editOmnichannelContact, editLivechatRoomCustomfields } = this.props;
@ -351,11 +377,32 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
} }
}; };
handleCreateDirectMessage = async (onPress: () => void) => {
try {
if (this.isDirect) {
await this.createDirect();
}
onPress();
} catch {
EventEmitter.emit(LISTENER, {
message: I18n.t('error-action-not-allowed', { action: I18n.t('Create_Direct_Messages') })
});
}
};
videoCall = () => { videoCall = () => {
const { room } = this.state; const { room } = this.state;
callJitsi(room); callJitsi(room);
}; };
handleBlockUser = async (rid: string, blocked: string, block: boolean) => {
logEvent(events.RI_TOGGLE_BLOCK_USER);
try {
await Services.toggleBlockUser(rid, blocked, block);
} catch (e) {
log(e);
}
};
renderAvatar = (room: ISubscription, roomUser: IUserParsed) => { renderAvatar = (room: ISubscription, roomUser: IUserParsed) => {
const { theme } = this.props; const { theme } = this.props;
@ -370,36 +417,54 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
); );
}; };
renderButton = (onPress: () => void, iconName: TIconsName, text: string) => { renderButton = (onPress: () => void, iconName: TIconsName, text: string, danger?: boolean) => {
const { theme } = this.props; const { theme } = this.props;
const color = danger ? themes[theme].dangerColor : themes[theme].actionTintColor;
const onActionPress = async () => {
try {
if (this.isDirect) {
await this.createDirect();
}
onPress();
} catch {
EventEmitter.emit(LISTENER, {
message: I18n.t('error-action-not-allowed', { action: I18n.t('Create_Direct_Messages') })
});
}
};
return ( return (
<BorderlessButton onPress={onActionPress} style={styles.roomButton}> <BorderlessButton testID={`room-info-view-${iconName}`} onPress={onPress} style={styles.roomButton}>
<CustomIcon name={iconName} size={30} color={themes[theme].actionTintColor} /> <CustomIcon name={iconName} size={30} color={color} />
<Text style={[styles.roomButtonText, { color: themes[theme].actionTintColor }]}>{text}</Text> <Text style={[styles.roomButtonText, { color }]}>{text}</Text>
</BorderlessButton> </BorderlessButton>
); );
}; };
renderButtons = () => { renderButtons = () => {
const { roomFromRid, roomUser } = this.state;
const { jitsiEnabled } = this.props; const { jitsiEnabled } = this.props;
const isFromDm = roomFromRid?.rid ? new RegExp(roomUser._id).test(roomFromRid.rid) : false;
const isDirectFromSaved = this.isDirect && this.fromRid && roomFromRid;
// Following the web behavior, when is a DM with myself, shouldn't appear block or ignore option
const isDmWithMyself = roomFromRid?.uids && roomFromRid.uids?.filter(uid => uid !== roomUser._id).length === 0;
const ignored = roomFromRid?.ignored;
const isIgnored = ignored?.includes?.(roomUser._id);
const blocker = roomFromRid?.blocker;
return ( return (
<View style={styles.roomButtonsContainer}> <View style={styles.roomButtonsContainer}>
{this.renderButton(this.goRoom, 'message', I18n.t('Message'))} {this.renderButton(() => this.handleCreateDirectMessage(this.goRoom), 'message', I18n.t('Message'))}
{jitsiEnabled && this.isDirect ? this.renderButton(this.videoCall, 'camera', I18n.t('Video_call')) : null} {jitsiEnabled && this.isDirect
? this.renderButton(() => this.handleCreateDirectMessage(this.videoCall), 'camera', I18n.t('Video_call'))
: null}
{isDirectFromSaved && !isFromDm && !isDmWithMyself
? this.renderButton(
() => handleIgnore(roomUser._id, !isIgnored, roomFromRid.rid),
'ignore',
I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
true
)
: null}
{isDirectFromSaved && isFromDm
? this.renderButton(
() => this.handleBlockUser(roomFromRid.rid, roomUser._id, !blocker),
'ignore',
I18n.t(`${blocker ? 'Unblock' : 'Block'}_user`),
true
)
: null}
</View> </View>
); );
}; };

View File

@ -217,20 +217,6 @@ export const handleRemoveUserFromRoom = async (
} }
}; };
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 ( export const handleOwner = async (
selectedUser: TUserModel, selectedUser: TUserModel,
isOwner: boolean, isOwner: boolean,

View File

@ -16,6 +16,7 @@ import { TSubscriptionModel, TUserModel } from '../../definitions';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { useAppSelector, usePermissions } from '../../lib/hooks'; import { useAppSelector, usePermissions } from '../../lib/hooks';
import { getRoomTitle, isGroupChat } from '../../lib/methods/helpers'; import { getRoomTitle, isGroupChat } from '../../lib/methods/helpers';
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import { showConfirmationAlert } from '../../lib/methods/helpers/info';
import log from '../../lib/methods/helpers/log'; import log from '../../lib/methods/helpers/log';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps'; import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
@ -28,7 +29,6 @@ import ActionsSection from './components/ActionsSection';
import { import {
fetchRole, fetchRole,
fetchRoomMembersRoles, fetchRoomMembersRoles,
handleIgnore,
handleLeader, handleLeader,
handleModerator, handleModerator,
handleMute, handleMute,
@ -207,7 +207,7 @@ const RoomMembersView = (): React.ReactElement => {
options.push({ options.push({
icon: 'ignore', icon: 'ignore',
title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'), title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
onPress: () => handleIgnore(selectedUser, !isIgnored, room.rid), onPress: () => handleIgnore(selectedUser._id, !isIgnored, room.rid),
testID: 'action-sheet-ignore-user' testID: 'action-sheet-ignore-user'
}); });
} }

View File

@ -1109,10 +1109,13 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
navToRoomInfo = (navParam: any) => { navToRoomInfo = (navParam: any) => {
const { navigation, user, isMasterDetail } = this.props; const { navigation, user, isMasterDetail } = this.props;
const { room } = this.state;
logEvent(events[`ROOM_GO_${navParam.t === 'd' ? 'USER' : 'ROOM'}_INFO`]); logEvent(events[`ROOM_GO_${navParam.t === 'd' ? 'USER' : 'ROOM'}_INFO`]);
if (navParam.rid === user.id) { if (navParam.rid === user.id) {
return; return;
} }
navParam.fromRid = room.rid;
if (isMasterDetail) { if (isMasterDetail) {
navParam.showCloseModal = true; navParam.showCloseModal = true;
// @ts-ignore // @ts-ignore

View File

@ -51,6 +51,9 @@ const data = {
detoxpublicprotected: { detoxpublicprotected: {
name: 'detox-public-protected', name: 'detox-public-protected',
joinCode: '123' joinCode: '123'
},
detoxpublicignore: {
name: `detox-public-ignore-${value}`
} }
}, },
groups: { groups: {

View File

@ -0,0 +1,103 @@
import { expect } from 'detox';
import data from '../../data';
import { navigateToLogin, login, searchRoom, sleep, platformTypes, TTextMatcher, tapBack } from '../../helpers/app';
import { sendMessage } from '../../helpers/data_setup';
async function navigateToRoom(user: string) {
await searchRoom(`${user}`);
await element(by.id(`rooms-list-view-item-${user}`)).tap();
await waitFor(element(by.id('room-view')))
.toBeVisible()
.withTimeout(5000);
}
async function navigateToInfoView() {
await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view')))
.toExist()
.withTimeout(5000);
await element(by.id('room-actions-info')).tap();
await waitFor(element(by.id('room-info-view')))
.toExist()
.withTimeout(2000);
}
describe('Ignore/Block User', () => {
const user = data.users.alternate.username;
let textMatcher: TTextMatcher;
before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password);
});
describe('Usage', () => {
describe('Block user from DM', () => {
it('should go to user info view', async () => {
await navigateToRoom(user);
await navigateToInfoView();
});
it('should block user', async () => {
await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Block user')))).toExist();
await element(by.id('room-info-view-ignore')).tap();
await waitFor(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Unblock user'))))
.toExist()
.withTimeout(2000);
await tapBack();
await waitFor(element(by.id('room-actions-view')))
.toBeVisible()
.withTimeout(5000);
await tapBack();
await expect(element(by[textMatcher]('This room is blocked'))).toExist();
});
it('should unblock user', async () => {
await navigateToInfoView();
await element(by.id('room-info-view-ignore')).tap();
await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Block user')))).toExist();
await tapBack();
await waitFor(element(by.id('room-actions-view')))
.toBeVisible()
.withTimeout(5000);
await tapBack();
await expect(element(by.id('messagebox'))).toBeVisible();
await tapBack();
});
});
describe('Ignore user from Message', () => {
it('should ignore user from message', async () => {
const channelName = data.channels.detoxpublicignore.name;
await navigateToRoom(channelName);
await element(by.id('room-view-join-button')).tap();
await sleep(300);
await sendMessage(data.users.alternate, channelName, 'message-01');
await sendMessage(data.users.alternate, channelName, 'message-02');
await waitFor(element(by[textMatcher](user)).atIndex(0))
.toExist()
.withTimeout(30000);
await sleep(300);
await element(by[textMatcher](user)).atIndex(0).tap();
await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Ignore')))).toExist();
await element(by.id('room-info-view-ignore')).tap();
await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Unignore')))).toExist();
await tapBack();
});
it('should tap to display message', async () => {
await expect(element(by[textMatcher]('Message ignored. Tap to display it.')).atIndex(0)).toExist();
await element(by[textMatcher]('Message ignored. Tap to display it.')).atIndex(0).tap();
await waitFor(element(by[textMatcher](user)))
.toBeVisible()
.withTimeout(1000);
await element(by[textMatcher](user)).atIndex(0).tap();
await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Unignore')))).toExist();
await element(by.id('room-info-view-ignore')).tap();
await expect(element(by.id('room-info-view-ignore').withDescendant(by[textMatcher]('Ignore')))).toExist();
await tapBack();
await expect(element(by[textMatcher]('message-02')).atIndex(0)).toBeVisible();
});
});
});
});