fix: fix bugs related to auto-translate and add tests (#5144)
* fix re-render on autoTranslateRoom sub update
* create autoTranslate tests
* create getMessageFromAttachment
* fix autoTranslate null value
* add translateLanguage to context
* fix type
* fix shouldComponentUpdate
* add autoTranslate and autoTranslateLanguage to subscription
* use getMessageFromAttachment instead att.description
* remove dequal
* add tryCatch
* 🙏
This commit is contained in:
parent
0c8b2f565e
commit
acbcac29c8
|
@ -322,7 +322,7 @@ const MessageActions = React.memo(
|
|||
const db = database.active;
|
||||
await db.write(async () => {
|
||||
await message.update(m => {
|
||||
m.autoTranslate = !m.autoTranslate;
|
||||
m.autoTranslate = m.autoTranslate !== null ? !m.autoTranslate : false;
|
||||
m._updatedAt = new Date();
|
||||
});
|
||||
});
|
||||
|
@ -479,7 +479,7 @@ const MessageActions = React.memo(
|
|||
// Toggle Auto-translate
|
||||
if (room.autoTranslate && message.u && message.u._id !== user.id) {
|
||||
options.push({
|
||||
title: I18n.t(message.autoTranslate ? 'View_Original' : 'Translate'),
|
||||
title: I18n.t(message.autoTranslate !== false ? 'View_Original' : 'Translate'),
|
||||
icon: 'language',
|
||||
onPress: () => handleToggleTranslation(message)
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import { IAttachment, TGetCustomEmoji } from '../../definitions';
|
|||
import CollapsibleQuote from './Components/CollapsibleQuote';
|
||||
import openLink from '../../lib/methods/helpers/openLink';
|
||||
import Markdown from '../markdown';
|
||||
import { getMessageFromAttachment } from './utils';
|
||||
|
||||
export type TElement = {
|
||||
type: string;
|
||||
|
@ -56,12 +57,14 @@ const AttachedActions = ({ attachment, getCustomEmoji }: { attachment: IAttachme
|
|||
const Attachments: React.FC<IMessageAttachments> = React.memo(
|
||||
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply, author }: IMessageAttachments) => {
|
||||
const { theme } = useTheme();
|
||||
const { translateLanguage } = useContext(MessageContext);
|
||||
|
||||
if (!attachments || attachments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const attachmentsElements = attachments.map((file: IAttachment, index: number) => {
|
||||
const msg = getMessageFromAttachment(file, translateLanguage);
|
||||
if (file && file.image_url) {
|
||||
return (
|
||||
<Image
|
||||
|
@ -72,6 +75,7 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
style={style}
|
||||
isReply={isReply}
|
||||
author={author}
|
||||
msg={msg}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -86,6 +90,7 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
style={style}
|
||||
theme={theme}
|
||||
author={author}
|
||||
msg={msg}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -99,6 +104,7 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
getCustomEmoji={getCustomEmoji}
|
||||
style={style}
|
||||
isReply={isReply}
|
||||
msg={msg}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -112,7 +118,9 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
);
|
||||
}
|
||||
|
||||
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />;
|
||||
return (
|
||||
<Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} msg={msg} />
|
||||
);
|
||||
});
|
||||
return <>{attachmentsElements}</>;
|
||||
},
|
||||
|
|
|
@ -40,6 +40,7 @@ interface IMessageAudioProps {
|
|||
getCustomEmoji: TGetCustomEmoji;
|
||||
scale?: number;
|
||||
author?: IUserMessage;
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
interface IMessageAudioState {
|
||||
|
@ -348,8 +349,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
|
||||
render() {
|
||||
const { loading, paused, currentTime, duration, cached } = this.state;
|
||||
const { file, getCustomEmoji, theme, scale, isReply, style } = this.props;
|
||||
const { description } = file;
|
||||
const { msg, getCustomEmoji, theme, scale, isReply, style } = this.props;
|
||||
// @ts-ignore can't use declare to type this
|
||||
const { baseUrl, user } = this.context;
|
||||
|
||||
|
@ -367,7 +367,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
return (
|
||||
<>
|
||||
<Markdown
|
||||
msg={description}
|
||||
msg={msg}
|
||||
style={[isReply && style]}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { StyleProp, TextStyle, View } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { dequal } from 'dequal';
|
||||
|
||||
import Touchable from './Touchable';
|
||||
import Markdown from '../markdown';
|
||||
import styles from './styles';
|
||||
import MessageContext from './Context';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { IAttachment, IUserMessage } from '../../definitions';
|
||||
import { useTheme } from '../../theme';
|
||||
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
|
||||
import { cancelDownload, downloadMediaFile, isDownloadActive, getMediaCache } from '../../lib/methods/handleMediaDownload';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
|
||||
import { cancelDownload, downloadMediaFile, getMediaCache, isDownloadActive } from '../../lib/methods/handleMediaDownload';
|
||||
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
|
||||
import { useTheme } from '../../theme';
|
||||
import Markdown from '../markdown';
|
||||
import BlurComponent from './Components/BlurComponent';
|
||||
import MessageContext from './Context';
|
||||
import Touchable from './Touchable';
|
||||
import styles from './styles';
|
||||
|
||||
interface IMessageButton {
|
||||
children: React.ReactElement;
|
||||
|
@ -29,6 +28,7 @@ interface IMessageImage {
|
|||
isReply?: boolean;
|
||||
getCustomEmoji?: TGetCustomEmoji;
|
||||
author?: IUserMessage;
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
const Button = React.memo(({ children, onPress, disabled }: IMessageButton) => {
|
||||
|
@ -61,124 +61,124 @@ export const MessageImage = React.memo(({ imgUri, cached, loading }: { imgUri: s
|
|||
);
|
||||
});
|
||||
|
||||
const ImageContainer = React.memo(
|
||||
({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply, author }: IMessageImage) => {
|
||||
const [imageCached, setImageCached] = useState(file);
|
||||
const [cached, setCached] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const getUrl = (link?: string) => imageUrl || formatAttachmentUrl(link, user.id, user.token, baseUrl);
|
||||
const img = getUrl(file.image_url);
|
||||
// The param file.title_link is the one that point to image with best quality, however we still need to test the imageUrl
|
||||
// And we cannot be certain whether the file.title_link actually exists.
|
||||
const imgUrlToCache = getUrl(imageCached.title_link || imageCached.image_url);
|
||||
const ImageContainer = ({
|
||||
file,
|
||||
imageUrl,
|
||||
showAttachment,
|
||||
getCustomEmoji,
|
||||
style,
|
||||
isReply,
|
||||
author,
|
||||
msg
|
||||
}: IMessageImage): React.ReactElement | null => {
|
||||
const [imageCached, setImageCached] = useState(file);
|
||||
const [cached, setCached] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const getUrl = (link?: string) => imageUrl || formatAttachmentUrl(link, user.id, user.token, baseUrl);
|
||||
const img = getUrl(file.image_url);
|
||||
// The param file.title_link is the one that point to image with best quality, however we still need to test the imageUrl
|
||||
// And we cannot be certain whether the file.title_link actually exists.
|
||||
const imgUrlToCache = getUrl(imageCached.title_link || imageCached.image_url);
|
||||
|
||||
useEffect(() => {
|
||||
const handleCache = async () => {
|
||||
if (img) {
|
||||
const cachedImageResult = await getMediaCache({
|
||||
type: 'image',
|
||||
mimeType: imageCached.image_type,
|
||||
urlToCache: imgUrlToCache
|
||||
});
|
||||
if (cachedImageResult?.exists) {
|
||||
setImageCached(prev => ({
|
||||
...prev,
|
||||
title_link: cachedImageResult?.uri
|
||||
}));
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
return;
|
||||
}
|
||||
if (isReply) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (isDownloadActive(imgUrlToCache)) {
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
await handleAutoDownload();
|
||||
}
|
||||
};
|
||||
handleCache();
|
||||
}, []);
|
||||
|
||||
if (!img) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleAutoDownload = async () => {
|
||||
const isCurrentUserAuthor = author?._id === user.id;
|
||||
const isAutoDownloadEnabled = fetchAutoDownloadEnabled('imagesPreferenceDownload');
|
||||
if (isAutoDownloadEnabled || isCurrentUserAuthor) {
|
||||
await handleDownload();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const imageUri = await downloadMediaFile({
|
||||
downloadUrl: imgUrlToCache,
|
||||
useEffect(() => {
|
||||
const handleCache = async () => {
|
||||
if (img) {
|
||||
const cachedImageResult = await getMediaCache({
|
||||
type: 'image',
|
||||
mimeType: imageCached.image_type
|
||||
mimeType: imageCached.image_type,
|
||||
urlToCache: imgUrlToCache
|
||||
});
|
||||
setImageCached(prev => ({
|
||||
...prev,
|
||||
title_link: imageUri
|
||||
}));
|
||||
setCached(true);
|
||||
} catch (e) {
|
||||
setCached(false);
|
||||
} finally {
|
||||
if (cachedImageResult?.exists) {
|
||||
setImageCached(prev => ({
|
||||
...prev,
|
||||
title_link: cachedImageResult?.uri
|
||||
}));
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
return;
|
||||
}
|
||||
if (isReply) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (isDownloadActive(imgUrlToCache)) {
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
await handleAutoDownload();
|
||||
}
|
||||
};
|
||||
handleCache();
|
||||
}, []);
|
||||
|
||||
const onPress = () => {
|
||||
if (loading && isDownloadActive(imgUrlToCache)) {
|
||||
cancelDownload(imgUrlToCache);
|
||||
setLoading(false);
|
||||
setCached(false);
|
||||
return;
|
||||
}
|
||||
if (!cached && !loading) {
|
||||
handleDownload();
|
||||
return;
|
||||
}
|
||||
if (!showAttachment) {
|
||||
return;
|
||||
}
|
||||
showAttachment(imageCached);
|
||||
};
|
||||
if (!img) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (imageCached.description) {
|
||||
return (
|
||||
<View>
|
||||
<Markdown
|
||||
msg={imageCached.description}
|
||||
style={[isReply && style]}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
/>
|
||||
<Button disabled={isReply} onPress={onPress}>
|
||||
<MessageImage imgUri={img} cached={cached} loading={loading} />
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
const handleAutoDownload = async () => {
|
||||
const isCurrentUserAuthor = author?._id === user.id;
|
||||
const isAutoDownloadEnabled = fetchAutoDownloadEnabled('imagesPreferenceDownload');
|
||||
if (isAutoDownloadEnabled || isCurrentUserAuthor) {
|
||||
await handleDownload();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const imageUri = await downloadMediaFile({
|
||||
downloadUrl: imgUrlToCache,
|
||||
type: 'image',
|
||||
mimeType: imageCached.image_type
|
||||
});
|
||||
setImageCached(prev => ({
|
||||
...prev,
|
||||
title_link: imageUri
|
||||
}));
|
||||
setCached(true);
|
||||
} catch (e) {
|
||||
setCached(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onPress = () => {
|
||||
if (loading && isDownloadActive(imgUrlToCache)) {
|
||||
cancelDownload(imgUrlToCache);
|
||||
setLoading(false);
|
||||
setCached(false);
|
||||
return;
|
||||
}
|
||||
if (!cached && !loading) {
|
||||
handleDownload();
|
||||
return;
|
||||
}
|
||||
if (!showAttachment) {
|
||||
return;
|
||||
}
|
||||
showAttachment(imageCached);
|
||||
};
|
||||
|
||||
if (msg) {
|
||||
return (
|
||||
<Button disabled={isReply} onPress={onPress}>
|
||||
<MessageImage imgUri={img} cached={cached} loading={loading} />
|
||||
</Button>
|
||||
<View>
|
||||
<Markdown msg={msg} style={[isReply && style]} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
<Button disabled={isReply} onPress={onPress}>
|
||||
<MessageImage imgUri={img} cached={cached} loading={loading} />
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button disabled={isReply} onPress={onPress}>
|
||||
<MessageImage imgUri={img} cached={cached} loading={loading} />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
ImageContainer.displayName = 'MessageImageContainer';
|
||||
MessageImage.displayName = 'MessageImage';
|
||||
|
|
|
@ -91,6 +91,7 @@ interface IMessageReply {
|
|||
timeFormat?: string;
|
||||
index: number;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
const Title = React.memo(
|
||||
|
@ -197,7 +198,7 @@ const Fields = React.memo(
|
|||
);
|
||||
|
||||
const Reply = React.memo(
|
||||
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
|
||||
({ attachment, timeFormat, index, getCustomEmoji, msg }: IMessageReply) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
||||
|
@ -238,7 +239,7 @@ const Reply = React.memo(
|
|||
style={[
|
||||
styles.button,
|
||||
index > 0 && styles.marginTop,
|
||||
attachment.description && styles.marginBottom,
|
||||
msg && styles.marginBottom,
|
||||
{
|
||||
borderColor
|
||||
}
|
||||
|
@ -271,7 +272,7 @@ const Reply = React.memo(
|
|||
) : null}
|
||||
</View>
|
||||
</Touchable>
|
||||
<Markdown msg={attachment.description} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
<Markdown msg={msg} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { StyleProp, StyleSheet, TextStyle, View, Text } from 'react-native';
|
||||
import { dequal } from 'dequal';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import messageStyles from './styles';
|
||||
|
@ -56,6 +55,7 @@ interface IMessageVideo {
|
|||
getCustomEmoji: TGetCustomEmoji;
|
||||
style?: StyleProp<TextStyle>[];
|
||||
isReply?: boolean;
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
const CancelIndicator = () => {
|
||||
|
@ -81,139 +81,130 @@ const Thumbnail = ({ loading, thumbnailUrl, cached }: { loading: boolean; thumbn
|
|||
</>
|
||||
);
|
||||
|
||||
const Video = React.memo(
|
||||
({ file, showAttachment, getCustomEmoji, style, isReply }: IMessageVideo) => {
|
||||
const [videoCached, setVideoCached] = useState(file);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [cached, setCached] = useState(false);
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const { theme } = useTheme();
|
||||
const video = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
|
||||
const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IMessageVideo): React.ReactElement | null => {
|
||||
const [videoCached, setVideoCached] = useState(file);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [cached, setCached] = useState(false);
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const { theme } = useTheme();
|
||||
const video = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
|
||||
|
||||
useEffect(() => {
|
||||
const handleVideoSearchAndDownload = async () => {
|
||||
if (video) {
|
||||
const cachedVideoResult = await getMediaCache({
|
||||
type: 'video',
|
||||
mimeType: file.video_type,
|
||||
urlToCache: video
|
||||
});
|
||||
const downloadActive = isDownloadActive(video);
|
||||
if (cachedVideoResult?.exists) {
|
||||
setVideoCached(prev => ({
|
||||
...prev,
|
||||
video_url: cachedVideoResult?.uri
|
||||
}));
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
if (downloadActive) {
|
||||
cancelDownload(video);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isReply) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
await handleAutoDownload();
|
||||
}
|
||||
};
|
||||
handleVideoSearchAndDownload();
|
||||
}, []);
|
||||
|
||||
if (!baseUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleAutoDownload = async () => {
|
||||
const isAutoDownloadEnabled = fetchAutoDownloadEnabled('videoPreferenceDownload');
|
||||
if (isAutoDownloadEnabled && file.video_type && isTypeSupported(file.video_type)) {
|
||||
await handleDownload();
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const videoUri = await downloadMediaFile({
|
||||
downloadUrl: video,
|
||||
useEffect(() => {
|
||||
const handleVideoSearchAndDownload = async () => {
|
||||
if (video) {
|
||||
const cachedVideoResult = await getMediaCache({
|
||||
type: 'video',
|
||||
mimeType: file.video_type
|
||||
mimeType: file.video_type,
|
||||
urlToCache: video
|
||||
});
|
||||
setVideoCached(prev => ({
|
||||
...prev,
|
||||
video_url: videoUri
|
||||
}));
|
||||
setCached(true);
|
||||
} catch {
|
||||
setCached(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
const downloadActive = isDownloadActive(video);
|
||||
if (cachedVideoResult?.exists) {
|
||||
setVideoCached(prev => ({
|
||||
...prev,
|
||||
video_url: cachedVideoResult?.uri
|
||||
}));
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
if (downloadActive) {
|
||||
cancelDownload(video);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isReply) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
await handleAutoDownload();
|
||||
}
|
||||
};
|
||||
handleVideoSearchAndDownload();
|
||||
}, []);
|
||||
|
||||
const onPress = async () => {
|
||||
if (file.video_type && cached && isTypeSupported(file.video_type) && showAttachment) {
|
||||
showAttachment(videoCached);
|
||||
return;
|
||||
}
|
||||
if (!loading && !cached && file.video_type && isTypeSupported(file.video_type)) {
|
||||
handleDownload();
|
||||
return;
|
||||
}
|
||||
if (loading && !cached) {
|
||||
handleCancelDownload();
|
||||
return;
|
||||
}
|
||||
if (!isIOS && file.video_url) {
|
||||
await downloadVideoToGallery(video);
|
||||
return;
|
||||
}
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Unsupported_format') });
|
||||
};
|
||||
if (!baseUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleCancelDownload = () => {
|
||||
if (loading) {
|
||||
cancelDownload(video);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const handleAutoDownload = async () => {
|
||||
const isAutoDownloadEnabled = fetchAutoDownloadEnabled('videoPreferenceDownload');
|
||||
if (isAutoDownloadEnabled && file.video_type && isTypeSupported(file.video_type)) {
|
||||
await handleDownload();
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const downloadVideoToGallery = async (uri: string) => {
|
||||
setLoading(true);
|
||||
const fileDownloaded = await fileDownload(uri, file);
|
||||
const handleDownload = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const videoUri = await downloadMediaFile({
|
||||
downloadUrl: video,
|
||||
type: 'video',
|
||||
mimeType: file.video_type
|
||||
});
|
||||
setVideoCached(prev => ({
|
||||
...prev,
|
||||
video_url: videoUri
|
||||
}));
|
||||
setCached(true);
|
||||
} catch {
|
||||
setCached(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (fileDownloaded) {
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
|
||||
return;
|
||||
}
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('error-save-video') });
|
||||
};
|
||||
const onPress = async () => {
|
||||
if (file.video_type && cached && isTypeSupported(file.video_type) && showAttachment) {
|
||||
showAttachment(videoCached);
|
||||
return;
|
||||
}
|
||||
if (!loading && !cached && file.video_type && isTypeSupported(file.video_type)) {
|
||||
handleDownload();
|
||||
return;
|
||||
}
|
||||
if (loading && !cached) {
|
||||
handleCancelDownload();
|
||||
return;
|
||||
}
|
||||
if (!isIOS && file.video_url) {
|
||||
await downloadVideoToGallery(video);
|
||||
return;
|
||||
}
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Unsupported_format') });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Markdown
|
||||
msg={file.description}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
style={[isReply && style]}
|
||||
theme={theme}
|
||||
/>
|
||||
<Touchable
|
||||
disabled={isReply}
|
||||
onPress={onPress}
|
||||
style={[styles.button, messageStyles.mustWrapBlur, { backgroundColor: themes[theme].videoBackground }]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
>
|
||||
<Thumbnail loading={loading} cached={cached} />
|
||||
</Touchable>
|
||||
</>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file)
|
||||
);
|
||||
const handleCancelDownload = () => {
|
||||
if (loading) {
|
||||
cancelDownload(video);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadVideoToGallery = async (uri: string) => {
|
||||
setLoading(true);
|
||||
const fileDownloaded = await fileDownload(uri, file);
|
||||
setLoading(false);
|
||||
|
||||
if (fileDownloaded) {
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
|
||||
return;
|
||||
}
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('error-save-video') });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Markdown msg={msg} username={user.username} getCustomEmoji={getCustomEmoji} style={[isReply && style]} theme={theme} />
|
||||
<Touchable
|
||||
disabled={isReply}
|
||||
onPress={onPress}
|
||||
style={[styles.button, messageStyles.mustWrapBlur, { backgroundColor: themes[theme].videoBackground }]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
>
|
||||
<Thumbnail loading={loading} cached={cached} />
|
||||
</Touchable>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Video;
|
||||
|
|
|
@ -95,7 +95,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
|
||||
shouldComponentUpdate(nextProps: IMessageContainerProps, nextState: IMessageContainerState) {
|
||||
const { isManualUnignored } = this.state;
|
||||
const { threadBadgeColor, isIgnored, highlighted, previousItem } = this.props;
|
||||
const { threadBadgeColor, isIgnored, highlighted, previousItem, autoTranslateRoom, autoTranslateLanguage } = this.props;
|
||||
|
||||
if (nextProps.highlighted !== highlighted) {
|
||||
return true;
|
||||
}
|
||||
|
@ -111,6 +112,12 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
if (nextProps.previousItem?._id !== previousItem?._id) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.autoTranslateRoom !== autoTranslateRoom) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.autoTranslateRoom !== autoTranslateRoom || nextProps.autoTranslateLanguage !== autoTranslateLanguage) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -382,14 +389,17 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
|
||||
let message = msg;
|
||||
let isTranslated = false;
|
||||
const otherUserMessage = u.username !== user.username;
|
||||
// "autoTranslateRoom" and "autoTranslateLanguage" are properties from the subscription
|
||||
// "autoTranslateMessage" is a toggle between "View Original" and "Translate" state
|
||||
if (autoTranslateRoom && autoTranslateMessage && autoTranslateLanguage) {
|
||||
if (autoTranslateRoom && autoTranslateMessage && autoTranslateLanguage && otherUserMessage) {
|
||||
const messageTranslated = getMessageTranslation(item, autoTranslateLanguage);
|
||||
isTranslated = !!messageTranslated;
|
||||
message = messageTranslated || message;
|
||||
}
|
||||
|
||||
const canTranslateMessage = autoTranslateRoom && autoTranslateLanguage && autoTranslateMessage !== false && otherUserMessage;
|
||||
|
||||
return (
|
||||
<MessageContext.Provider
|
||||
value={{
|
||||
|
@ -409,7 +419,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
jumpToMessage,
|
||||
threadBadgeColor,
|
||||
toggleFollowThread,
|
||||
replies
|
||||
replies,
|
||||
translateLanguage: canTranslateMessage ? autoTranslateLanguage : undefined
|
||||
}}
|
||||
>
|
||||
{/* @ts-ignore*/}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable complexity */
|
||||
import { IAttachment } from '../../definitions';
|
||||
import { MessageTypesValues, TMessageModel } from '../../definitions/IMessage';
|
||||
import I18n from '../../i18n';
|
||||
import { DISCUSSION } from './constants';
|
||||
|
@ -194,3 +195,14 @@ export const getMessageTranslation = (message: TMessageModel, autoTranslateLangu
|
|||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getMessageFromAttachment = (attachment: IAttachment, translateLanguage?: string): string | undefined => {
|
||||
let msg = attachment.description;
|
||||
if (translateLanguage) {
|
||||
const translatedMessage = attachment.translations?.[translateLanguage];
|
||||
if (translatedMessage) {
|
||||
msg = translatedMessage;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IUser } from './IUser';
|
||||
import { IAttachmentTranslations } from './IMessage';
|
||||
|
||||
export interface IAttachment {
|
||||
ts?: string | Date;
|
||||
|
@ -29,6 +30,7 @@ export interface IAttachment {
|
|||
thumb_url?: string;
|
||||
collapsed?: boolean;
|
||||
audio_type?: string;
|
||||
translations?: IAttachmentTranslations;
|
||||
}
|
||||
|
||||
export interface IServerAttachment {
|
||||
|
|
|
@ -41,7 +41,7 @@ export interface IEditedBy {
|
|||
|
||||
export type TOnLinkPress = (link: string) => void;
|
||||
|
||||
export interface ITranslations {
|
||||
export interface IMessageTranslations {
|
||||
_id: string;
|
||||
language: string;
|
||||
value: string;
|
||||
|
@ -136,7 +136,7 @@ export interface IMessage extends IMessageFromServer {
|
|||
replies?: string[];
|
||||
unread?: boolean;
|
||||
autoTranslate?: boolean;
|
||||
translations?: ITranslations[];
|
||||
translations?: IMessageTranslations[];
|
||||
tmsg?: string;
|
||||
blocks?: any;
|
||||
e2e?: E2EType;
|
||||
|
@ -243,3 +243,7 @@ export type MessageTypesValues =
|
|||
| 'message_pinned'
|
||||
| 'message_snippeted'
|
||||
| 'jitsi_call_started';
|
||||
|
||||
export interface IAttachmentTranslations {
|
||||
[k: string]: string;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { store } from '../../store/auxStore';
|
||||
import { IAttachment, IMessage } from '../../../definitions';
|
||||
import { IAttachment, IAttachmentTranslations, IMessage } from '../../../definitions';
|
||||
import { getAvatarURL } from '../../methods/helpers';
|
||||
|
||||
export function createQuoteAttachment(message: IMessage, messageLink: string): IAttachment {
|
||||
|
@ -8,7 +8,8 @@ export function createQuoteAttachment(message: IMessage, messageLink: string): I
|
|||
|
||||
return {
|
||||
text: message.msg,
|
||||
...('translations' in message && { translations: message?.translations }),
|
||||
// this type is wrong
|
||||
...('translations' in message && { translations: message?.translations as unknown as IAttachmentTranslations }),
|
||||
message_link: messageLink,
|
||||
author_name: message.alias || message.u.username,
|
||||
author_icon: getAvatarURL({
|
||||
|
|
|
@ -100,15 +100,20 @@ const AutoTranslateView = (): React.ReactElement => {
|
|||
title={name || language}
|
||||
onPress={() => saveAutoTranslateLanguage(language)}
|
||||
testID={`auto-translate-view-${language}`}
|
||||
right={() => (selectedLanguage === language ? <List.Icon name='check' color={colors.tintColor} /> : null)}
|
||||
right={() =>
|
||||
selectedLanguage === language ? (
|
||||
<List.Icon testID={`auto-translate-view-${language}-check`} name='check' color={colors.tintColor} />
|
||||
) : null
|
||||
}
|
||||
translateTitle={false}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<SafeAreaView testID='auto-translate-view'>
|
||||
<SafeAreaView>
|
||||
<StatusBar />
|
||||
<FlatList
|
||||
testID='auto-translate-view'
|
||||
data={languages}
|
||||
keyExtractor={item => item.name || item.language}
|
||||
renderItem={({ item: { language, name } }) => <LanguageItem language={language} name={name} />}
|
||||
|
@ -117,9 +122,13 @@ const AutoTranslateView = (): React.ReactElement => {
|
|||
<List.Separator />
|
||||
<List.Item
|
||||
title='Enable_Auto_Translate'
|
||||
testID='auto-translate-view-switch'
|
||||
right={() => (
|
||||
<Switch value={enableAutoTranslate} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleAutoTranslate} />
|
||||
<Switch
|
||||
testID='auto-translate-view-switch'
|
||||
value={enableAutoTranslate}
|
||||
trackColor={SWITCH_TRACK_COLOR}
|
||||
onValueChange={toggleAutoTranslate}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<List.Separator />
|
||||
|
|
|
@ -57,6 +57,8 @@ export interface IListContainerProps {
|
|||
navigation: any; // TODO: type me
|
||||
showMessageInMainThread: boolean;
|
||||
serverVersion: string | null;
|
||||
autoTranslateRoom?: boolean;
|
||||
autoTranslateLanguage?: string;
|
||||
}
|
||||
|
||||
interface IListContainerState {
|
||||
|
@ -106,7 +108,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
|
|||
|
||||
shouldComponentUpdate(nextProps: IListContainerProps, nextState: IListContainerState) {
|
||||
const { refreshing, highlightedMessage } = this.state;
|
||||
const { hideSystemMessages, tunread, ignored, loading } = this.props;
|
||||
const { hideSystemMessages, tunread, ignored, loading, autoTranslateLanguage, autoTranslateRoom } = this.props;
|
||||
if (loading !== nextProps.loading) {
|
||||
return true;
|
||||
}
|
||||
|
@ -125,6 +127,9 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
|
|||
if (!dequal(ignored, nextProps.ignored)) {
|
||||
return true;
|
||||
}
|
||||
if (autoTranslateLanguage !== nextProps.autoTranslateLanguage || autoTranslateRoom !== nextProps.autoTranslateRoom) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,9 @@ const roomAttrsUpdate = [
|
|||
'status',
|
||||
'lastMessage',
|
||||
'onHold',
|
||||
't'
|
||||
't',
|
||||
'autoTranslate',
|
||||
'autoTranslateLanguage'
|
||||
] as TRoomUpdate[];
|
||||
|
||||
interface IRoomViewProps extends IActionSheetProvider, IBaseScreen<ChatsStackParamList, 'RoomView'> {
|
||||
|
@ -1491,7 +1493,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
|
||||
render() {
|
||||
console.count(`${this.constructor.name}.render calls`);
|
||||
const { room, loading } = this.state;
|
||||
const { room, loading, canAutoTranslate } = this.state;
|
||||
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, serverVersion } = this.props;
|
||||
const { rid, t } = room;
|
||||
let sysMes;
|
||||
|
@ -1520,6 +1522,8 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
hideSystemMessages={Array.isArray(sysMes) ? sysMes : Hide_System_Messages}
|
||||
showMessageInMainThread={user.showMessageInMainThread ?? false}
|
||||
serverVersion={serverVersion}
|
||||
autoTranslateRoom={canAutoTranslate && 'id' in room && room.autoTranslate}
|
||||
autoTranslateLanguage={'id' in room ? room.autoTranslateLanguage : undefined}
|
||||
/>
|
||||
{this.renderFooter()}
|
||||
{this.renderActions()}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import axios from 'axios';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
import data from '../data';
|
||||
import random from './random';
|
||||
|
@ -161,30 +161,15 @@ export interface IDeleteCreateUser {
|
|||
username: string;
|
||||
}
|
||||
|
||||
const deleteCreatedUser = async ({ server, username: usernameToDelete }: IDeleteCreateUser) => {
|
||||
const serverConnection = axios.create({
|
||||
baseURL: `${server}/api/v1/`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
});
|
||||
console.log(`Logging in as admin in ${server}`);
|
||||
const response = await serverConnection.post('login', {
|
||||
user: data.adminUser,
|
||||
password: data.adminPassword
|
||||
});
|
||||
const { authToken, userId } = response.data.data;
|
||||
serverConnection.defaults.headers.common['X-User-Id'] = userId;
|
||||
serverConnection.defaults.headers.common['X-Auth-Token'] = authToken;
|
||||
|
||||
console.log(`Get user info: users.info?username=${usernameToDelete}`);
|
||||
const result = await serverConnection.get(`users.info?username=${usernameToDelete}`);
|
||||
const userIdToDelete = result.data.user._id;
|
||||
|
||||
const body = { userId: userIdToDelete, confirmRelinquish: false };
|
||||
console.log(`Delete user: users.delete ${JSON.stringify(body)}`);
|
||||
const responsePost = await serverConnection.post('users.delete', body);
|
||||
return responsePost.data;
|
||||
const deleteCreatedUser = async ({ username: usernameToDelete }: IDeleteCreateUser) => {
|
||||
try {
|
||||
const api = await initApi(data.adminUser, data.adminPassword);
|
||||
const result = await api.get(`users.info?username=${usernameToDelete}`);
|
||||
const responsePost = await api.post('users.delete', { userId: result.data.user._id, confirmRelinquish: true });
|
||||
return responsePost.data;
|
||||
} catch (error) {
|
||||
console.log(JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
|
||||
// Delete created users to avoid use all the Seats Available on the server
|
||||
|
@ -195,3 +180,20 @@ export const deleteCreatedUsers = async (deleteUsersAfterAll: IDeleteCreateUser[
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const initApi = async (user: string, password: string): Promise<AxiosInstance> => {
|
||||
const api = axios.create({
|
||||
baseURL: `${server}/api/v1/`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
});
|
||||
const response = await api.post('login', {
|
||||
user,
|
||||
password
|
||||
});
|
||||
const { authToken, userId } = response.data.data;
|
||||
api.defaults.headers.common['X-User-Id'] = userId;
|
||||
api.defaults.headers.common['X-Auth-Token'] = authToken;
|
||||
return api;
|
||||
};
|
|
@ -0,0 +1,227 @@
|
|||
import { by, device, element, expect, waitFor } from 'detox';
|
||||
|
||||
import { TTextMatcher, login, navigateToLogin, platformTypes, searchRoom, tapBack, tryTapping } from '../../helpers/app';
|
||||
import { ITestUser, createRandomRoom, createRandomUser, initApi } from '../../helpers/data_setup';
|
||||
import random from '../../helpers/random';
|
||||
|
||||
const roomId = '64b846e4760e618aa9f91ab7';
|
||||
|
||||
const sendMessageOnTranslationTestRoom = async (msg: string): Promise<{ user: ITestUser; msgId: string }> => {
|
||||
const user = await createRandomUser();
|
||||
const api = await initApi(user.username, user.password);
|
||||
|
||||
const msgId = random();
|
||||
|
||||
await api.post('channels.join', { roomId, joinCode: null });
|
||||
await api.post('chat.sendMessage', {
|
||||
message: { _id: msgId, rid: roomId, msg, tshow: false }
|
||||
});
|
||||
|
||||
return { user, msgId };
|
||||
};
|
||||
|
||||
const deleteMessageOnTranslationTestRoom = async ({ user, msgId }: { user: ITestUser; msgId: string }): Promise<void> => {
|
||||
const api = await initApi(user.username, user.password);
|
||||
await api.post('chat.delete', {
|
||||
msgId,
|
||||
roomId
|
||||
});
|
||||
};
|
||||
|
||||
async function navigateToRoom(roomName: string) {
|
||||
await searchRoom(`${roomName}`);
|
||||
await element(by.id(`rooms-list-view-item-${roomName}`)).tap();
|
||||
await waitFor(element(by.id('room-view')))
|
||||
.toBeVisible()
|
||||
.withTimeout(5000);
|
||||
}
|
||||
|
||||
export function waitForVisible(id: string) {
|
||||
return waitFor(element(by.id(id)))
|
||||
.toBeVisible()
|
||||
.withTimeout(5000);
|
||||
}
|
||||
|
||||
export function waitForVisibleTextMatcher(msg: string, textMatcher: TTextMatcher) {
|
||||
return waitFor(element(by[textMatcher](msg)).atIndex(0))
|
||||
.toExist()
|
||||
.withTimeout(5000);
|
||||
}
|
||||
|
||||
export function waitForNotVisible(id: string) {
|
||||
return waitFor(element(by.id(id)))
|
||||
.not.toBeVisible()
|
||||
.withTimeout(5000);
|
||||
}
|
||||
|
||||
describe('Auto Translate', () => {
|
||||
let textMatcher: TTextMatcher;
|
||||
|
||||
const languages = {
|
||||
default: 'en',
|
||||
translated: 'pt'
|
||||
};
|
||||
|
||||
const oldMessage = {
|
||||
[languages.default]: 'dog',
|
||||
[languages.translated]: 'cachorro'
|
||||
};
|
||||
|
||||
const newMessage = {
|
||||
[languages.default]: 'cat',
|
||||
[languages.translated]: 'gato'
|
||||
};
|
||||
|
||||
const attachmentMessage = {
|
||||
[languages.default]: 'attachment',
|
||||
[languages.translated]: 'anexo'
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const user = await createRandomUser();
|
||||
await createRandomRoom(user);
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
({ textMatcher } = platformTypes[device.getPlatform()]);
|
||||
await navigateToLogin();
|
||||
await login(user.username, user.password);
|
||||
});
|
||||
|
||||
it('should join translation-test room', async () => {
|
||||
await navigateToRoom('translation-test');
|
||||
await element(by.id('room-view-join-button')).tap();
|
||||
await waitForNotVisible('room-view-join-button');
|
||||
await tapBack();
|
||||
await navigateToRoom('translation-test');
|
||||
await waitForVisible('messagebox');
|
||||
await expect(element(by.id('room-view-join'))).not.toBeVisible();
|
||||
});
|
||||
|
||||
it('should see old message not translated before enable auto translate', async () => {
|
||||
await waitForVisibleTextMatcher(oldMessage[languages.default] as string, textMatcher);
|
||||
await waitForVisibleTextMatcher(attachmentMessage[languages.default] as string, textMatcher);
|
||||
});
|
||||
|
||||
it('should enable auto translate', async () => {
|
||||
await element(by.id('room-header')).tap();
|
||||
|
||||
await waitForVisible('room-actions-view');
|
||||
await element(by.id('room-actions-view')).swipe('up');
|
||||
|
||||
await waitForVisible('room-actions-auto-translate');
|
||||
await element(by.id('room-actions-auto-translate')).tap();
|
||||
|
||||
await waitForVisible('auto-translate-view-switch');
|
||||
await element(by.id('auto-translate-view-switch')).tap();
|
||||
|
||||
// verify default language is checked
|
||||
await waitFor(element(by.id(`auto-translate-view-${languages.default}`)))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('auto-translate-view'))
|
||||
.scroll(750, 'down');
|
||||
await waitForVisible(`auto-translate-view-${languages.default}-check`);
|
||||
|
||||
// enable translated language
|
||||
await waitFor(element(by.id(`auto-translate-view-${languages.translated}`)))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('auto-translate-view'))
|
||||
.scroll(750, 'down');
|
||||
await waitForNotVisible(`auto-translate-view-${languages.translated}-check`);
|
||||
await element(by.id(`auto-translate-view-${languages.translated}`)).tap();
|
||||
await waitForVisible(`auto-translate-view-${languages.translated}-check`);
|
||||
|
||||
// verify default language is unchecked
|
||||
await waitFor(element(by.id(`auto-translate-view-${languages.default}`)))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('auto-translate-view'))
|
||||
.scroll(750, 'up');
|
||||
await waitForNotVisible(`auto-translate-view-${languages.default}-check`);
|
||||
|
||||
await tapBack();
|
||||
await tapBack();
|
||||
});
|
||||
|
||||
it('should see old message translated after enable auto translate', async () => {
|
||||
await waitForVisibleTextMatcher(oldMessage[languages.translated] as string, textMatcher);
|
||||
await waitForVisibleTextMatcher(attachmentMessage[languages.translated] as string, textMatcher);
|
||||
});
|
||||
|
||||
it('should see new message translated', async () => {
|
||||
const randomMatcher = random();
|
||||
const data = await sendMessageOnTranslationTestRoom(`${newMessage[languages.default]} - ${randomMatcher}`);
|
||||
await waitForVisibleTextMatcher(`${newMessage[languages.translated]} - ${randomMatcher}`, textMatcher);
|
||||
await deleteMessageOnTranslationTestRoom(data);
|
||||
});
|
||||
|
||||
it('should see original message', async () => {
|
||||
const randomMatcher = random();
|
||||
const data = await sendMessageOnTranslationTestRoom(`${newMessage[languages.default]} - ${randomMatcher}`);
|
||||
await waitForVisibleTextMatcher(`${newMessage[languages.translated]} - ${randomMatcher}`, textMatcher);
|
||||
|
||||
await tryTapping(element(by[textMatcher](`${newMessage[languages.translated]} - ${randomMatcher}`)).atIndex(0), 2000, true);
|
||||
|
||||
await waitForVisible('action-sheet-handle');
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
|
||||
await waitForVisibleTextMatcher('View original', textMatcher);
|
||||
await element(by[textMatcher]('View original')).atIndex(0).tap();
|
||||
|
||||
await waitForVisibleTextMatcher(`${newMessage[languages.default]} - ${randomMatcher}`, textMatcher);
|
||||
|
||||
await deleteMessageOnTranslationTestRoom(data);
|
||||
});
|
||||
|
||||
it('disable auto translate and see original message', async () => {
|
||||
const randomMatcher = random();
|
||||
const data = await sendMessageOnTranslationTestRoom(`${newMessage[languages.default]} - ${randomMatcher}`);
|
||||
|
||||
await waitForVisibleTextMatcher(`${newMessage[languages.translated]} - ${randomMatcher}`, textMatcher);
|
||||
|
||||
await element(by.id('room-header')).tap();
|
||||
await waitForVisible('room-actions-view');
|
||||
await element(by.id('room-actions-view')).swipe('up');
|
||||
|
||||
await waitForVisible('room-actions-auto-translate');
|
||||
await element(by.id('room-actions-auto-translate')).tap();
|
||||
|
||||
await waitForVisible('auto-translate-view-switch');
|
||||
await element(by.id('auto-translate-view-switch')).tap();
|
||||
|
||||
await tapBack();
|
||||
await tapBack();
|
||||
|
||||
await waitForVisibleTextMatcher(`${newMessage[languages.default]} - ${randomMatcher}`, textMatcher);
|
||||
|
||||
await deleteMessageOnTranslationTestRoom(data);
|
||||
});
|
||||
|
||||
it(`should don't see action to View original when disable auto translate`, async () => {
|
||||
await waitForVisibleTextMatcher(oldMessage[languages.default] as string, textMatcher);
|
||||
await tryTapping(element(by[textMatcher](oldMessage[languages.default] as string)).atIndex(0), 2000, true);
|
||||
|
||||
await waitForVisible('action-sheet-handle');
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
|
||||
await waitForNotVisible('View original');
|
||||
// close action sheet
|
||||
await element(by.id('room-header')).tap();
|
||||
});
|
||||
|
||||
it('should the language selected when activating auto translate again must be the old one', async () => {
|
||||
await element(by.id('room-header')).tap();
|
||||
await waitForVisible('room-actions-view');
|
||||
await element(by.id('room-actions-view')).swipe('up');
|
||||
|
||||
await waitForVisible('room-actions-auto-translate');
|
||||
await element(by.id('room-actions-auto-translate')).tap();
|
||||
|
||||
await waitForVisible('auto-translate-view-switch');
|
||||
await element(by.id('auto-translate-view-switch')).tap();
|
||||
|
||||
// verify translated language is checked and is the old one
|
||||
await waitFor(element(by.id(`auto-translate-view-${languages.translated}`)))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('auto-translate-view'))
|
||||
.scroll(750, 'down');
|
||||
await waitForVisible(`auto-translate-view-${languages.translated}-check`);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue