[NEW] Reply in direct (#4582)

* add translations

* implements reply in DM

* refactor data e2e

* fix ignoreuser e2e

* create test for reply in dm

* minor tweak

Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>
Co-authored-by: Reinaldo Neto <reinaldonetof@hotmail.com>
This commit is contained in:
Gleidson Daniel Silva 2022-11-04 14:09:58 -03:00 committed by GitHub
parent 4f960e9fb4
commit 3a9a98bdfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 104 additions and 9 deletions

View File

@ -17,7 +17,7 @@ import Header, { HEADER_HEIGHT, IHeader } from './Header';
import events from '../../lib/methods/helpers/log/events'; import events from '../../lib/methods/helpers/log/events';
import { IApplicationState, IEmoji, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions'; import { IApplicationState, IEmoji, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
import { getPermalinkMessage } from '../../lib/methods'; import { getPermalinkMessage } from '../../lib/methods';
import { hasPermission } from '../../lib/methods/helpers'; import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
export interface IMessageActionsProps { export interface IMessageActionsProps {
@ -41,6 +41,7 @@ export interface IMessageActionsProps {
deleteMessagePermission?: string[]; deleteMessagePermission?: string[];
forceDeleteMessagePermission?: string[]; forceDeleteMessagePermission?: string[];
pinMessagePermission?: string[]; pinMessagePermission?: string[];
createDirectMessagePermission?: string[];
} }
export interface IMessageActions { export interface IMessageActions {
@ -70,7 +71,8 @@ const MessageActions = React.memo(
editMessagePermission, editMessagePermission,
deleteMessagePermission, deleteMessagePermission,
forceDeleteMessagePermission, forceDeleteMessagePermission,
pinMessagePermission pinMessagePermission,
createDirectMessagePermission
}, },
ref ref
) => { ) => {
@ -235,6 +237,23 @@ const MessageActions = React.memo(
replyInit(message, false); replyInit(message, false);
}; };
const handleReplyInDM = async (message: TAnyMessageModel) => {
if (message?.u?.username) {
const result = await Services.createDirectMessage(message.u.username);
if (result.success) {
const { room } = result;
const params = {
rid: room.rid,
name: getRoomTitle(room),
t: room.t,
roomUserId: getUidDirectMessage(room),
replyInDM: message
};
Navigation.replace('RoomView', params);
}
}
};
const handleStar = async (message: TAnyMessageModel) => { const handleStar = async (message: TAnyMessageModel) => {
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR); logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
try { try {
@ -345,6 +364,15 @@ const MessageActions = React.memo(
}); });
} }
// Reply in DM
if (room.t !== 'd' && room.t !== 'l' && createDirectMessagePermission) {
options.push({
title: I18n.t('Reply_in_direct_message'),
icon: 'arrow-back',
onPress: () => handleReplyInDM(message)
});
}
// Edit // Edit
if (allowEdit(message)) { if (allowEdit(message)) {
options.push({ options.push({
@ -480,7 +508,8 @@ const mapStateToProps = (state: IApplicationState) => ({
editMessagePermission: state.permissions['edit-message'], editMessagePermission: state.permissions['edit-message'],
deleteMessagePermission: state.permissions['delete-message'], deleteMessagePermission: state.permissions['delete-message'],
forceDeleteMessagePermission: state.permissions['force-delete-message'], forceDeleteMessagePermission: state.permissions['force-delete-message'],
pinMessagePermission: state.permissions['pin-message'] pinMessagePermission: state.permissions['pin-message'],
createDirectMessagePermission: state.permissions['create-d']
}); });
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions); export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);

View File

@ -862,6 +862,7 @@
"Select_Members": "Select Members", "Select_Members": "Select Members",
"Also_send_thread_message_to_channel_behavior": "Also send thread message to channel", "Also_send_thread_message_to_channel_behavior": "Also send thread message to channel",
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the Also send to channel behavior", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the Also send to channel behavior",
"Reply_in_direct_message": "Reply in Direct Message",
"room_archived": "archived room", "room_archived": "archived room",
"room_unarchived": "unarchived room" "room_unarchived": "unarchived room"
} }

View File

@ -817,6 +817,7 @@
"Select_Members": "Selecionar Membros", "Select_Members": "Selecionar Membros",
"Also_send_thread_message_to_channel_behavior": "Também enviar mensagem do tópico para o canal", "Also_send_thread_message_to_channel_behavior": "Também enviar mensagem do tópico para o canal",
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal",
"Reply_in_direct_message": "Responder por mensagem direta",
"room_archived": "{{username}} arquivou a sala", "room_archived": "{{username}} arquivou a sala",
"room_unarchived": "{{username}} desarquivou a sala" "room_unarchived": "{{username}} desarquivou a sala"
} }

View File

@ -5,7 +5,7 @@ import { IItem } from '../views/TeamChannelsView';
import { IOptionsField } from '../views/NotificationPreferencesView/options'; import { IOptionsField } from '../views/NotificationPreferencesView/options';
import { IServer } from '../definitions/IServer'; import { IServer } from '../definitions/IServer';
import { IAttachment } from '../definitions/IAttachment'; import { IAttachment } from '../definitions/IAttachment';
import { IMessage, TMessageModel } from '../definitions/IMessage'; import { IMessage, TAnyMessageModel, TMessageModel } from '../definitions/IMessage';
import { ISubscription, SubscriptionType, TSubscriptionModel } from '../definitions/ISubscription'; import { ISubscription, SubscriptionType, TSubscriptionModel } from '../definitions/ISubscription';
import { ICannedResponse } from '../definitions/ICannedResponse'; import { ICannedResponse } from '../definitions/ICannedResponse';
import { TDataSelect } from '../definitions/IDataSelect'; import { TDataSelect } from '../definitions/IDataSelect';
@ -37,6 +37,7 @@ export type ChatsStackParamList = {
roomUserId?: string | null; roomUserId?: string | null;
usedCannedResponse?: string; usedCannedResponse?: string;
status?: string; status?: string;
replyInDM?: TAnyMessageModel;
} }
| undefined; // Navigates back to RoomView already on stack | undefined; // Navigates back to RoomView already on stack
RoomActionsView: { RoomActionsView: {

View File

@ -225,6 +225,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
private retryFindTimeout?: ReturnType<typeof setTimeout>; private retryFindTimeout?: ReturnType<typeof setTimeout>;
private messageErrorActions?: IMessageErrorActions | null; private messageErrorActions?: IMessageErrorActions | null;
private messageActions?: IMessageActions | null; private messageActions?: IMessageActions | null;
private replyInDM?: TAnyMessageModel;
// Type of InteractionManager.runAfterInteractions // Type of InteractionManager.runAfterInteractions
private didMountInteraction?: { private didMountInteraction?: {
then: (onfulfilled?: (() => any) | undefined, onrejected?: (() => any) | undefined) => Promise<any>; then: (onfulfilled?: (() => any) | undefined, onrejected?: (() => any) | undefined) => Promise<any>;
@ -259,6 +260,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
this.jumpToMessageId = props.route.params?.jumpToMessageId; this.jumpToMessageId = props.route.params?.jumpToMessageId;
this.jumpToThreadId = props.route.params?.jumpToThreadId; this.jumpToThreadId = props.route.params?.jumpToThreadId;
const roomUserId = props.route.params?.roomUserId ?? getUidDirectMessage(room); const roomUserId = props.route.params?.roomUserId ?? getUidDirectMessage(room);
this.replyInDM = props.route.params?.replyInDM;
this.state = { this.state = {
joined: true, joined: true,
room, room,
@ -332,6 +334,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
if (isIOS && this.rid) { if (isIOS && this.rid) {
this.updateUnreadCount(); this.updateUnreadCount();
} }
if (this.replyInDM) {
this.onReplyInit(this.replyInDM, false);
}
}); });
if (isTablet) { if (isTablet) {
EventEmitter.addEventListener(KEY_COMMAND, this.handleCommands); EventEmitter.addEventListener(KEY_COMMAND, this.handleCommands);

View File

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

View File

@ -54,6 +54,11 @@ const data = {
joinCode: '123' joinCode: '123'
} }
}, },
userRegularChannels: {
detoxpublic: {
name: `detox-public-${value}`
}
},
groups: { groups: {
private: { private: {
name: `detox-private-${value}` name: `detox-private-${value}`

View File

@ -53,6 +53,11 @@ const data = {
joinCode: '123' joinCode: '123'
} }
}, },
userRegularChannels: {
detoxpublic: {
name: `detox-public-${value}`
}
},
groups: { groups: {
private: { private: {
name: `detox-private-${value}` name: `detox-private-${value}`

View File

@ -158,6 +158,21 @@ const setup = async () => {
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
for (const channelKey in data.userRegularChannels) {
if (Object.prototype.hasOwnProperty.call(data.userRegularChannels, channelKey)) {
const channel = data.userRegularChannels[channelKey as TDataChannels];
const {
data: {
channel: { _id }
}
} = await createChannelIfNotExists(channel.name);
if ('joinCode' in channel) {
await changeChannelJoinCode(_id, channel.joinCode);
}
}
}
for (const groupKey in data.groups) { for (const groupKey in data.groups) {
if (Object.prototype.hasOwnProperty.call(data.groups, groupKey)) { if (Object.prototype.hasOwnProperty.call(data.groups, groupKey)) {
const group = data.groups[groupKey as TDataGroups]; const group = data.groups[groupKey as TDataGroups];

View File

@ -13,6 +13,7 @@ import {
platformTypes, platformTypes,
TTextMatcher TTextMatcher
} from '../../helpers/app'; } from '../../helpers/app';
import { sendMessage } from '../../helpers/data_setup';
async function navigateToRoom(roomName: string) { async function navigateToRoom(roomName: string) {
await searchRoom(`${roomName}`); await searchRoom(`${roomName}`);
@ -496,6 +497,37 @@ describe('Room screen', () => {
await waitFor(element(by[textMatcher](`${data.random}delete`)).atIndex(0)) await waitFor(element(by[textMatcher](`${data.random}delete`)).atIndex(0))
.toNotExist() .toNotExist()
.withTimeout(2000); .withTimeout(2000);
await tapBack();
});
it('should reply in DM to another user', async () => {
const channelName = data.userRegularChannels.detoxpublic.name;
const stringToReply = 'Message to reply in DM';
await waitFor(element(by.id('rooms-list-view')))
.toBeVisible()
.withTimeout(2000);
await navigateToRoom(channelName);
await sendMessage(data.users.alternate, channelName, stringToReply);
await waitFor(element(by[textMatcher](stringToReply)).atIndex(0))
.toBeVisible()
.withTimeout(3000);
await element(by[textMatcher](stringToReply)).atIndex(0).longPress();
await waitFor(element(by.id('action-sheet')))
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await waitFor(element(by[textMatcher]('Reply in Direct Message')).atIndex(0))
.toExist()
.withTimeout(6000);
await element(by[textMatcher]('Reply in Direct Message')).atIndex(0).tap();
await waitFor(element(by.id(`room-view-title-${data.users.alternate.username}`)))
.toExist()
.withTimeout(6000);
await element(by.id('messagebox-input')).replaceText(`${data.random} replied in dm`);
await waitFor(element(by.id('messagebox-send-message')))
.toExist()
.withTimeout(2000);
await element(by.id('messagebox-send-message')).tap();
}); });
}); });
}); });

View File

@ -69,9 +69,8 @@ describe('Ignore/Block User', () => {
}); });
describe('Ignore user from Message', () => { describe('Ignore user from Message', () => {
it('should ignore user from message', async () => { it('should ignore user from message', async () => {
const channelName = data.channels.detoxpublicignore.name; const channelName = data.userRegularChannels.detoxpublic.name;
await navigateToRoom(channelName); await navigateToRoom(channelName);
await element(by.id('room-view-join-button')).tap();
await sleep(300); await sleep(300);
await sendMessage(data.users.alternate, channelName, 'message-01'); await sendMessage(data.users.alternate, channelName, 'message-01');
await sendMessage(data.users.alternate, channelName, 'message-02'); await sendMessage(data.users.alternate, channelName, 'message-02');