[IMPROVE] Remove show message in main thread preference (#4435)

* [IMPROVE] Remove show message in main thread preference

* default settings

* created the get

* fix compare server version

* fix E2E tests

* settings to logged user

* remove constant and get alsosendtochannel from user

* fix send to channel first message

* fix when the alsoSendThreadToChannel is checked

* added list picker user preference

* tweaks in messagebox tmid and detox

* added pt-br and deleted expectToBeVisible id

* reactive alsoSendThreadTOChannel

* fix the behavior when press or long press threads messages

* remove reply in thread from threads

* clean helpers detox

* tweak onMessageLongPress and onSubmit

* Remove unnecessary calculations inside ListPicker

* Fix long press logic

* Fix onReplyInit logic

* fix data_setup at detox for servers greater than 5.0

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Reinaldo Neto 2022-08-25 16:53:19 -03:00 committed by GitHub
parent ba15bc9fe6
commit ccbc84f9a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 174 additions and 14 deletions

View File

@ -330,7 +330,7 @@ const MessageActions = React.memo(
let options: TActionSheetOptionsItem[] = []; let options: TActionSheetOptionsItem[] = [];
// Reply // Reply
if (!isReadOnly) { if (!isReadOnly && !tmid) {
options = [ options = [
{ {
title: I18n.t('Reply_in_Thread'), title: I18n.t('Reply_in_Thread'),

View File

@ -55,7 +55,7 @@ import {
} from '../../definitions'; } from '../../definitions';
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods'; import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
import { hasPermission, debounce, isAndroid, isIOS, isTablet } from '../../lib/methods/helpers'; import { hasPermission, debounce, isAndroid, isIOS, isTablet, compareServerVersion } from '../../lib/methods/helpers';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { TSupportedThemes } from '../../theme'; import { TSupportedThemes } from '../../theme';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
@ -111,8 +111,8 @@ export interface IMessageBoxProps extends IBaseScreen<ChatsStackParamList & Mast
isActionsEnabled: boolean; isActionsEnabled: boolean;
usedCannedResponse: string; usedCannedResponse: string;
uploadFilePermission: string[]; uploadFilePermission: string[];
serverVersion: string;
goToCannedResponses: () => void | null; goToCannedResponses: () => void | null;
serverVersion: string;
} }
interface IMessageBoxState { interface IMessageBoxState {
@ -181,7 +181,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
commandPreview: [], commandPreview: [],
showCommandPreview: false, showCommandPreview: false,
command: {}, command: {},
tshow: false, tshow: this.sendThreadToChannel,
mentionLoading: false, mentionLoading: false,
permissionToUpload: true permissionToUpload: true
}; };
@ -211,6 +211,23 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
} }
get sendThreadToChannel() {
const { user, serverVersion, tmid } = this.props;
if (tmid && compareServerVersion(serverVersion, 'lowerThan', '5.0.0')) {
return false;
}
if (tmid && user.alsoSendThreadToChannel === 'default') {
return false;
}
if (user.alsoSendThreadToChannel === 'always') {
return true;
}
if (user.alsoSendThreadToChannel === 'never') {
return false;
}
return true;
}
async componentDidMount() { async componentDidMount() {
const db = database.active; const db = database.active;
const { rid, tmid, navigation, sharing, usedCannedResponse, isMasterDetail } = this.props; const { rid, tmid, navigation, sharing, usedCannedResponse, isMasterDetail } = this.props;
@ -381,7 +398,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
} }
componentDidUpdate(prevProps: IMessageBoxProps) { componentDidUpdate(prevProps: IMessageBoxProps) {
const { uploadFilePermission, goToCannedResponses } = this.props; const { uploadFilePermission, goToCannedResponses, replyWithMention, threadsEnabled } = this.props;
if (prevProps.replyWithMention !== replyWithMention) {
if (threadsEnabled && replyWithMention) {
this.setState({ tshow: this.sendThreadToChannel });
}
}
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission) || prevProps.goToCannedResponses !== goToCannedResponses) { if (!dequal(prevProps.uploadFilePermission, uploadFilePermission) || prevProps.goToCannedResponses !== goToCannedResponses) {
this.setOptions(); this.setOptions();
} }
@ -687,9 +709,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
clearInput = () => { clearInput = () => {
const { tshow } = this.state;
const { user, serverVersion } = this.props;
this.setInput(''); this.setInput('');
this.setShowSend(false); this.setShowSend(false);
this.setState({ tshow: false }); if (compareServerVersion(serverVersion, 'lowerThan', '5.0.0') || (tshow && user.alsoSendThreadToChannel === 'default')) {
this.setState({ tshow: false });
}
}; };
canUploadFile = (file: any) => { canUploadFile = (file: any) => {
@ -974,7 +1000,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
// Normal message // Normal message
} else { } else {
// @ts-ignore // @ts-ignore
onSubmit(message, undefined, tshow); onSubmit(message, undefined, tmid ? tshow : false);
} }
}; };
@ -1044,7 +1070,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
onPress={this.onPressSendToChannel} onPress={this.onPressSendToChannel}
testID='messagebox-send-to-channel' testID='messagebox-send-to-channel'
> >
<CustomIcon name={tshow ? 'checkbox-checked' : 'checkbox-unchecked'} size={24} color={themes[theme].auxiliaryText} /> <CustomIcon
testID={tshow ? 'send-to-channel-checked' : 'send-to-channel-unchecked'}
name={tshow ? 'checkbox-checked' : 'checkbox-unchecked'}
size={24}
color={themes[theme].auxiliaryText}
/>
<Text style={[styles.sendToChannelText, { color: themes[theme].auxiliaryText }]}> <Text style={[styles.sendToChannelText, { color: themes[theme].auxiliaryText }]}>
{I18n.t('Messagebox_Send_to_channel')} {I18n.t('Messagebox_Send_to_channel')}
</Text> </Text>
@ -1213,7 +1244,8 @@ const mapStateToProps = (state: IApplicationState) => ({
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList, FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize, FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled, Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
uploadFilePermission: state.permissions['mobile-upload-file'] uploadFilePermission: state.permissions['mobile-upload-file'],
serverVersion: state.server.version
}); });
const dispatchToProps = { const dispatchToProps = {

View File

@ -21,6 +21,7 @@ export interface ILoggedUser {
showMessageInMainThread?: boolean; showMessageInMainThread?: boolean;
isFromWebView?: boolean; isFromWebView?: boolean;
enableMessageParserEarlyAdoption: boolean; enableMessageParserEarlyAdoption: boolean;
alsoSendThreadToChannel: 'default' | 'always' | 'never';
} }
export interface ILoggedUserResultFromServer export interface ILoggedUserResultFromServer

View File

@ -842,5 +842,7 @@
"error-init-video-conf": "Error starting video call", "error-init-video-conf": "Error starting video call",
"totp-invalid": "Code or password invalid", "totp-invalid": "Code or password invalid",
"Close_Chat": "Close Chat", "Close_Chat": "Close Chat",
"Select_tags": "Select tags" "Select_tags": "Select tags",
"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"
} }

View File

@ -795,5 +795,7 @@
"Show_badge_for_mentions_Info": "Mostrar contador somente para menções diretas", "Show_badge_for_mentions_Info": "Mostrar contador somente para menções diretas",
"totp-invalid": "Código ou senha inválida", "totp-invalid": "Código ou senha inválida",
"Close_Chat": "Fechar Conversa", "Close_Chat": "Fechar Conversa",
"Select_tags": "Selecionar tag(s)" "Select_tags": "Selecionar tag(s)",
"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"
} }

View File

@ -295,6 +295,9 @@ export default function subscribeRooms() {
if ((['settings.preferences.showMessageInMainThread'] as any) in diff) { if ((['settings.preferences.showMessageInMainThread'] as any) in diff) {
store.dispatch(setUser({ showMessageInMainThread: diff['settings.preferences.showMessageInMainThread'] })); store.dispatch(setUser({ showMessageInMainThread: diff['settings.preferences.showMessageInMainThread'] }));
} }
if ((['settings.preferences.alsoSendThreadToChannel'] as any) in diff) {
store.dispatch(setUser({ alsoSendThreadToChannel: diff['settings.preferences.alsoSendThreadToChannel'] }));
}
} }
if (/subscriptions/.test(ev)) { if (/subscriptions/.test(ev)) {
if (type === 'removed') { if (type === 'removed') {

View File

@ -268,8 +268,10 @@ async function login(credentials: ICredentials, isFromWebView = false): Promise<
const result = sdk.current.currentLogin?.result; const result = sdk.current.currentLogin?.result;
let enableMessageParserEarlyAdoption = true; let enableMessageParserEarlyAdoption = true;
let showMessageInMainThread = false;
if (compareServerVersion(serverVersion, 'lowerThan', '5.0.0')) { if (compareServerVersion(serverVersion, 'lowerThan', '5.0.0')) {
enableMessageParserEarlyAdoption = result.me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true; enableMessageParserEarlyAdoption = result.me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true;
showMessageInMainThread = result.me.settings?.preferences?.showMessageInMainThread ?? true;
} }
if (result) { if (result) {
@ -287,8 +289,9 @@ async function login(credentials: ICredentials, isFromWebView = false): Promise<
roles: result.me.roles, roles: result.me.roles,
avatarETag: result.me.avatarETag, avatarETag: result.me.avatarETag,
isFromWebView, isFromWebView,
showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true, showMessageInMainThread,
enableMessageParserEarlyAdoption enableMessageParserEarlyAdoption,
alsoSendThreadToChannel: result.me.settings?.preferences?.alsoSendThreadToChannel
}; };
return user; return user;
} }

View File

@ -813,6 +813,10 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
}; };
onReplyInit = (message: TAnyMessageModel, mention: boolean) => { onReplyInit = (message: TAnyMessageModel, mention: boolean) => {
// If there's a thread already, we redirect to it
if (mention && !!message.tlm) {
return this.onThreadPress(message);
}
this.setState({ this.setState({
selectedMessage: message, selectedMessage: message,
replying: true, replying: true,
@ -833,6 +837,10 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
}; };
onMessageLongPress = (message: TAnyMessageModel) => { onMessageLongPress = (message: TAnyMessageModel) => {
// if it's a thread message on main room, we disable the long press
if (message.tmid && !this.tmid) {
return;
}
this.messagebox?.current?.closeEmojiAndAction(this.messageActions?.showMessageActions, message); this.messagebox?.current?.closeEmojiAndAction(this.messageActions?.showMessageActions, message);
}; };

View File

@ -0,0 +1,81 @@
import React, { useState } from 'react';
import { StyleSheet, Text } from 'react-native';
import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet';
import { CustomIcon } from '../../containers/CustomIcon';
import * as List from '../../containers/List';
import I18n from '../../i18n';
import { useTheme } from '../../theme';
import sharedStyles from '../Styles';
const styles = StyleSheet.create({
title: { ...sharedStyles.textRegular, fontSize: 16 }
});
const OPTIONS = {
alsoSendThreadToChannel: [
{
label: 'Default',
value: 'default'
},
{
label: 'Always',
value: 'always'
},
{
label: 'Never',
value: 'never'
}
]
};
type TOptions = keyof typeof OPTIONS;
interface IBaseParams {
preference: TOptions;
value: string;
onChangeValue: (param: { [key: string]: string }, onError: () => void) => void;
}
const ListPicker = ({
preference,
value,
title,
testID,
onChangeValue
}: {
title: string;
testID: string;
} & IBaseParams) => {
const { showActionSheet, hideActionSheet } = useActionSheet();
const { colors } = useTheme();
const [option, setOption] = useState(
value ? OPTIONS[preference].find(option => option.value === value) : OPTIONS[preference][0]
);
const getOptions = (): TActionSheetOptionsItem[] =>
OPTIONS[preference].map(i => ({
title: I18n.t(i.label, { defaultValue: i.label }),
onPress: () => {
hideActionSheet();
onChangeValue({ [preference]: i.value.toString() }, () => setOption(option));
setOption(i);
},
right: option?.value === i.value ? () => <CustomIcon name={'check'} size={20} color={colors.tintActive} /> : undefined
}));
return (
<List.Item
title={title}
testID={testID}
onPress={() => showActionSheet({ options: getOptions() })}
right={() => (
<Text style={[styles.title, { color: colors.actionTintColor }]}>
{option?.label ? I18n.t(option?.label, { defaultValue: option?.label }) : option?.label}
</Text>
)}
/>
);
};
export default ListPicker;

View File

@ -15,13 +15,14 @@ import { getUserSelector } from '../../selectors/login';
import { ProfileStackParamList } from '../../stacks/types'; import { ProfileStackParamList } from '../../stacks/types';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { useAppSelector } from '../../lib/hooks'; import { useAppSelector } from '../../lib/hooks';
import ListPicker from './ListPicker';
interface IUserPreferencesViewProps { interface IUserPreferencesViewProps {
navigation: StackNavigationProp<ProfileStackParamList, 'UserPreferencesView'>; navigation: StackNavigationProp<ProfileStackParamList, 'UserPreferencesView'>;
} }
const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Element => { const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Element => {
const { enableMessageParserEarlyAdoption, id } = useAppSelector(state => getUserSelector(state)); const { enableMessageParserEarlyAdoption, id, alsoSendThreadToChannel } = useAppSelector(state => getUserSelector(state));
const serverVersion = useAppSelector(state => state.server.version); const serverVersion = useAppSelector(state => state.server.version);
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -45,6 +46,16 @@ const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Ele
} }
}; };
const setAlsoSendThreadToChannel = async (param: { [key: string]: string }, onError: () => void) => {
try {
await Services.saveUserPreferences(param);
dispatch(setUser(param));
} catch (e) {
log(e);
onError();
}
};
const renderMessageParserSwitch = (value: boolean) => ( const renderMessageParserSwitch = (value: boolean) => (
<Switch value={value} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleMessageParser} /> <Switch value={value} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleMessageParser} />
); );
@ -74,6 +85,20 @@ const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Ele
<List.Separator /> <List.Separator />
</List.Section> </List.Section>
) : null} ) : null}
{compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0') ? (
<List.Section title='Also_send_thread_message_to_channel_behavior'>
<List.Separator />
<ListPicker
onChangeValue={setAlsoSendThreadToChannel}
preference='alsoSendThreadToChannel'
value={alsoSendThreadToChannel}
title='Messagebox_Send_to_channel'
testID='preferences-view-enable-message-parser'
/>
<List.Separator />
<List.Info info='Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description' />
</List.Section>
) : null}
</List.Container> </List.Container>
</SafeAreaView> </SafeAreaView>
); );

View File

@ -1,6 +1,7 @@
const axios = require('axios').default; const axios = require('axios').default;
const data = require('../data'); const data = require('../data');
const random = require('./random');
const TEAM_TYPE = { const TEAM_TYPE = {
PUBLIC: 0, PUBLIC: 0,
@ -106,6 +107,8 @@ const changeChannelJoinCode = async (roomId, joinCode) => {
try { try {
await rocketchat.post('method.call/saveRoomSettings', { await rocketchat.post('method.call/saveRoomSettings', {
message: JSON.stringify({ message: JSON.stringify({
msg: 'method',
id: random(10),
method: 'saveRoomSettings', method: 'saveRoomSettings',
params: [roomId, { joinCode }] params: [roomId, { joinCode }]
}) })