Merge branch 'develop' into 4.38.0-single-server

# Conflicts:
#	ios/RocketChatRN.xcodeproj/project.pbxproj
This commit is contained in:
Diego Mello 2023-05-12 11:51:17 -03:00
commit abcbd4b406
72 changed files with 391 additions and 400 deletions

View File

@ -7,7 +7,7 @@ orbs:
macos: &macos
macos:
xcode: "14.2.0"
resource_class: large
resource_class: macos.m1.large.gen1
bash-env: &bash-env
BASH_ENV: "~/.nvm/nvm.sh"

View File

@ -9,9 +9,9 @@ module.exports = {
},
artifacts: {
plugins: {
screenshot: 'failing',
video: 'failing',
uiHierarchy: 'enabled'
screenshot: process.env.CI ? undefined : 'failing',
video: process.env.CI ? undefined : 'failing',
uiHierarchy: process.env.CI ? undefined : 'enabled'
}
},
apps: {

View File

@ -18,7 +18,7 @@ exports[`Storyshots Avatar Custom Style 1`] = `"{\\"type\\":\\"View\\",\\"props\
exports[`Storyshots Avatar Direct 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/diego.mello?format=png&size=112\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]}"`;
exports[`Storyshots Avatar Emoji 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},[{\\"width\\":30,\\"height\\":30},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/emoji-custom/troll.jpg\\",\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"contain\\"},\\"children\\":null}]}]}"`;
exports[`Storyshots Avatar Emoji 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},[{\\"width\\":30,\\"height\\":30}]]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/emoji-custom/troll.jpg\\",\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"contain\\"},\\"children\\":null}]}]}"`;
exports[`Storyshots Avatar Static 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]}"`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -147,7 +147,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.37.1"
versionName "4.38.0"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]

View File

@ -79,10 +79,4 @@
</intent-filter>
</activity>
</application>
<queries>
<package android:name="org.jitsi.meet" />
<intent>
<action android:name="android.intent.action.SEND" />
</intent>
</queries>
</manifest>

View File

@ -138,7 +138,7 @@ public class ReplyBroadcast extends BroadcastReceiver {
final Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
int smallIconResId = res.getIdentifier("ic_notification", "drawable", packageName);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(channel);

View File

@ -6,7 +6,7 @@ import scrollPersistTaps from '../lib/methods/helpers/scrollPersistTaps';
interface IKeyboardViewProps extends KeyboardAwareScrollViewProps {
keyboardVerticalOffset?: number;
scrollEnabled?: boolean;
children: React.ReactElement[] | React.ReactElement | null | (React.ReactElement | null)[];
children: React.ReactElement[] | React.ReactElement;
}
const KeyboardView = ({ style, contentContainerStyle, scrollEnabled, keyboardVerticalOffset, children }: IKeyboardViewProps) => (

View File

@ -39,7 +39,7 @@ const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemPro
.join(', ');
}
if (count > 3) {
displayNames = `${displayNames} ${I18n.t('and_more')} ${count - 3}`;
displayNames = `${displayNames} ${I18n.t('and_N_more', { count: count - 3 })}`;
} else {
displayNames = displayNames.replace(/,(?=[^,]*$)/, ` ${I18n.t('and')}`);
}

View File

@ -21,7 +21,7 @@ const Emoji = React.memo(({ literal, isMessageContainsOnlyEmoji, getCustomEmoji,
const emojiUnicode = shortnameToUnicode(literal);
const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
if (emoji && customEmojis) {
return <CustomEmoji style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]} emoji={emoji} />;
return <CustomEmoji style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={emoji} />;
}
return (
<Text style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}>

View File

@ -3,6 +3,7 @@ import { StyleSheet, Text, View } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import styles from './styles';
const style = StyleSheet.create({
container: {
@ -43,7 +44,7 @@ const ListItem = React.memo(({ children, level, bulletWidth, continue: _continue
return (
<View style={style.container}>
<View style={[{ width: bulletWidth }, style.bullet]}>
<Text style={{ color: themes[theme].bodyText }}>{bullet}</Text>
<Text style={[styles.text, styles.listPrefix, { color: themes[theme].bodyText }]}>{bullet}</Text>
</View>
<View style={style.contents}>{children}</View>
</View>

View File

@ -16,8 +16,8 @@ const OrderedList = ({ value }: IOrderedListProps): React.ReactElement => {
<View>
{value.map(item => (
<View style={styles.row} key={item.number?.toString()}>
<Text style={[styles.text, { color: colors.bodyText }]}>{item.number}. </Text>
<Text style={[styles.inline, { color: colors.bodyText }]}>
<Text style={[styles.text, styles.listPrefix, { color: colors.bodyText }]}>{item.number}. </Text>
<Text style={[styles.text, styles.inline, { color: colors.bodyText }]}>
<Inline value={item.value} />
</Text>
</View>

View File

@ -17,8 +17,8 @@ const UnorderedList = ({ value }: IUnorderedListProps) => {
<View>
{value.map(item => (
<View style={styles.row}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>- </Text>
<Text style={[styles.inline, { color: themes[theme].bodyText }]}>
<Text style={[styles.text, styles.listPrefix, { color: themes[theme].bodyText }]}>- </Text>
<Text style={[styles.text, styles.inline, { color: themes[theme].bodyText }]}>
<Inline value={item.value} />
</Text>
</View>

View File

@ -162,5 +162,8 @@ export default StyleSheet.create({
},
inline: {
flexShrink: 1
},
listPrefix: {
fontVariant: ['tabular-nums']
}
});

View File

@ -1,6 +1,5 @@
import React, { useContext } from 'react';
import { dequal } from 'dequal';
import { Text } from 'react-native';
import { IMessageAttachments } from './interfaces';
import Image from './Image';
@ -8,13 +7,12 @@ import Audio from './Audio';
import Video from './Video';
import Reply from './Reply';
import Button from '../Button';
import styles from './styles';
import MessageContext from './Context';
import { useTheme } from '../../theme';
import { IAttachment } from '../../definitions';
import { IAttachment, TGetCustomEmoji } from '../../definitions';
import CollapsibleQuote from './Components/CollapsibleQuote';
import openLink from '../../lib/methods/helpers/openLink';
import { themes } from '../../lib/constants';
import Markdown from '../markdown';
export type TElement = {
type: string;
@ -23,9 +21,8 @@ export type TElement = {
text: string;
};
const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
const AttachedActions = ({ attachment, getCustomEmoji }: { attachment: IAttachment; getCustomEmoji: TGetCustomEmoji }) => {
const { onAnswerButtonPress } = useContext(MessageContext);
const { theme } = useTheme();
if (!attachment.actions) {
return null;
@ -50,7 +47,7 @@ const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
});
return (
<>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{attachment.text}</Text>
<Markdown msg={attachment.text} getCustomEmoji={getCustomEmoji} />
{attachedButtons}
</>
);
@ -106,7 +103,7 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
}
if (file && file.actions && file.actions.length > 0) {
return <AttachedActions attachment={file} />;
return <AttachedActions attachment={file} getCustomEmoji={getCustomEmoji} />;
}
if (typeof file.collapsed === 'boolean') {
return (

View File

@ -1,37 +1,33 @@
import React, { useRef } from 'react';
import React from 'react';
import { messageBlockWithContext } from '../UIKit/MessageBlock';
import { IMessageBlocks } from './interfaces';
const Blocks = React.memo(({ blocks, id: mid, rid, blockAction }: IMessageBlocks) => {
const Blocks = ({ blocks, id: mid, rid, blockAction }: IMessageBlocks) => {
if (blocks && blocks.length > 0) {
const appId = blocks[0]?.appId || '';
// eslint-disable-next-line react-hooks/rules-of-hooks
const comp = useRef(
React.createElement(
messageBlockWithContext({
action: async ({ actionId, value, blockId }: { actionId: string; value: string; blockId: string }) => {
if (blockAction) {
await blockAction({
actionId,
appId,
value,
blockId,
rid,
mid
});
}
},
appId,
rid
}),
{ blocks }
)
return React.createElement(
messageBlockWithContext({
action: async ({ actionId, value, blockId }: { actionId: string; value: string; blockId: string }) => {
if (blockAction) {
await blockAction({
actionId,
appId,
value,
blockId,
rid,
mid
});
}
},
appId,
rid
}),
{ blocks }
);
return comp.current;
}
return null;
});
};
Blocks.displayName = 'MessageBlocks';

View File

@ -508,6 +508,24 @@ export const MessageWithReply = () => (
}
]}
/>
<Message
msg='Yes, I am'
attachments={[
{
author_name: 'rocket.cat',
attachments: [
{
author_name: 'rocket.cat',
ts: date,
timeFormat: 'LT',
description: 'Are you seeing this mario :marioparty: ?',
image_url: 'https://octodex.github.com/images/yaktocat.png'
}
],
text: ''
}
]}
/>
</>
);
@ -857,21 +875,38 @@ export const Ignored = () => <Message isIgnored />;
export const CustomStyle = () => <Message msg='Message' style={[{ backgroundColor: '#ddd' }]} />;
export const ShowButtonAsAttachment = () => (
<Message
attachments={[
{
text: 'Test Button',
actions: [
{
type: 'button',
text: 'Text button',
msg: 'Response message',
msg_in_chat_window: true
}
]
}
]}
/>
<>
<Message
attachments={[
{
text: 'Test Button',
actions: [
{
type: 'button',
text: 'Text button',
msg: 'Response message',
msg_in_chat_window: true
}
]
}
]}
/>
<Message
attachments={[
{
text: ':avocado: **Message with markdown**\n\n_Some text_\n\nThis is a test',
actions: [
{
type: 'button',
text: 'Text button',
msg: 'Response message',
msg_in_chat_window: true
}
]
}
]}
/>
</>
);
export const ThumbnailFromServer = () => (

View File

@ -1,6 +1,5 @@
import React from 'react';
import { Keyboard, ViewStyle } from 'react-native';
import { Subscription } from 'rxjs';
import Message from './Message';
import MessageContext from './Context';
@ -78,13 +77,16 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
state = { isManualUnignored: false };
private subscription?: Subscription;
private subscription?: Function;
componentDidMount() {
const { item } = this.props;
if (item && item.observe) {
const observable = item.observe();
this.subscription = observable.subscribe(() => {
// @ts-ignore
if (item && item.experimentalSubscribe) {
// TODO: Update watermelonDB to recognize experimentalSubscribe at types
// experimentalSubscribe(subscriber: (isDeleted: boolean) => void, debugInfo?: any): Unsubscribe
// @ts-ignore
this.subscription = item.experimentalSubscribe(() => {
this.forceUpdate();
});
}
@ -112,8 +114,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
}
componentWillUnmount() {
if (this.subscription && this.subscription.unsubscribe) {
this.subscription.unsubscribe();
if (this.subscription) {
this.subscription();
}
}

View File

@ -102,6 +102,7 @@ export interface ISubscription {
onHold?: boolean;
source?: IOmnichannelSource;
hideMentionStatus?: boolean;
usersCount?: number;
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
messages: RelationModified<TMessageModel>;
threads: RelationModified<TThreadModel>;

View File

@ -9,7 +9,6 @@ declare module 'react-native-config-reader';
declare module 'react-native-keycommands';
declare module 'react-native-mime-types';
declare module 'react-native-restart';
declare module 'react-native-jitsi-meet';
declare module 'rn-root-view';
declare module 'react-native-math-view';
declare module '@env' {

View File

@ -634,5 +634,6 @@
"Logout_failed": "فشل تسجيل الخروج!",
"Log_analytics_events": "تحليلات سجل الأحداث",
"invalid-room": "غرفة غير صالحة",
"Broadcast_hint": "يمكن فقط للمستخدمين المصرح لهم كتابة رسائل جديدة، ولكن سيتمكن المستخدمون الآخرون من الرد"
"Broadcast_hint": "يمكن فقط للمستخدمين المصرح لهم كتابة رسائل جديدة، ولكن سيتمكن المستخدمون الآخرون من الرد",
"and_N_more": "و{{count}} آخرين"
}

View File

@ -858,5 +858,6 @@
"Channel_hint_not_read_only": "Alle Nutzer im Kanal können neue Nachrichten schreiben",
"Channel_hint_encrypted_not_available": "Nicht verfügbar in öffentlichen Kanälen",
"Read_only_hint": "Nur autorisierte Benutzer können neue Nachrichten schreiben",
"Discussion": "Diskussion"
"Discussion": "Diskussion",
"and_N_more": "und {{count}} weitere"
}

View File

@ -899,5 +899,6 @@
"admin-video-conf-provider-not-configured-body": "Configure conference calls in order to make it available on this workspace.",
"Presence_Cap_Warning_Title": "User status temporarily disabled",
"Presence_Cap_Warning_Description": "Active connections have reached the limit for the workspace, thus the service that handles user status is disabled. It can be re-enabled manually in workspace settings.",
"Learn_more": "Learn more"
"Learn_more": "Learn more",
"and_N_more": "and {{count}} more"
}

View File

@ -425,5 +425,6 @@
"Server_selection_numbers": "Seleccionar servidor 1...9",
"Add_server": "Añadir servidor",
"New_line": "Nueva línea",
"Broadcast_hint": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos."
"Broadcast_hint": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.",
"and_N_more": "y {{count}} más"
}

View File

@ -876,5 +876,6 @@
"Call": "Soita",
"Reply_in_direct_message": "Vastaa suoralla viestillä",
"room_archived": "arkistoi huoneen",
"room_unarchived": "palautti huoneen arkistosta"
"room_unarchived": "palautti huoneen arkistosta",
"and_N_more": "ja {{count}} lisää"
}

View File

@ -791,5 +791,6 @@
"Omnichannel_on_hold_chat_resumed": "Le chat en attente a repris : {{comment}}",
"Omnichannel_queue": "File d'attente omnicanale",
"Empty": "Vide",
"Broadcast_hint": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre."
"Broadcast_hint": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.",
"and_N_more": "et {{count}} de plus"
}

View File

@ -690,5 +690,6 @@
"Workspace_URL_Example": "Es. tua-azienda.rocket.chat",
"invalid-room": "Canale non valido",
"Open_Livechats": "Chat in corso",
"Broadcast_hint": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere"
"Broadcast_hint": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere",
"and_N_more": "e altri {{count}}"
}

View File

@ -547,5 +547,6 @@
"Clear": "クリア",
"This_will_clear_all_your_offline_data": "オフラインデータをすべて削除します。",
"invalid-room": "無効なルーム",
"Broadcast_hint": "許可されたユーザーのみが新しいメッセージを書き込めます。他のユーザーは返信することができます"
"Broadcast_hint": "許可されたユーザーのみが新しいメッセージを書き込めます。他のユーザーは返信することができます",
"and_N_more": "さらに{{count}}つ"
}

View File

@ -791,5 +791,6 @@
"Omnichannel_on_hold_chat_resumed": "Chat in de wacht hervat: {{comment}}",
"Omnichannel_queue": "Omnichannel-wachtrij",
"Empty": "Leeg",
"Broadcast_hint": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden"
"Broadcast_hint": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden",
"and_N_more": "en nog {{count}}"
}

View File

@ -885,5 +885,6 @@
"Discard":"Descartar",
"Discard_changes_description":"Todas as alterações serão perdidas, se você sair sem salvar.",
"Presence_Cap_Warning_Title": "Status do usuário desabilitado temporariamente",
"Presence_Cap_Warning_Description": "O limite de conexões ativas para a workspace foi atingido, por isso o serviço responsável pela presença dos usuários está temporariamente desabilitado. Ele pode ser reabilitado manualmente nas configurações da workspace."
"Presence_Cap_Warning_Description": "O limite de conexões ativas para a workspace foi atingido, por isso o serviço responsável pela presença dos usuários está temporariamente desabilitado. Ele pode ser reabilitado manualmente nas configurações da workspace.",
"and_N_more": "e mais {{count}}"
}

View File

@ -494,5 +494,6 @@
"You": "Você",
"You_will_not_be_able_to_recover_this_message": "Você será incapaz de recuperar esta mensagem!",
"Open_Livechats": "Chats em andamento",
"Broadcast_hint": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder"
"Broadcast_hint": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder",
"and_N_more": "e mais {{count}}"
}

View File

@ -844,5 +844,6 @@
"Calling": "Идет вызов",
"Start_a_call": "Начать звонок",
"Call": "Звонок",
"Reply_in_direct_message": "Ответить в личном сообщении"
"Reply_in_direct_message": "Ответить в личном сообщении",
"and_N_more": "и еще {{count}}"
}

View File

@ -809,5 +809,6 @@
"error-init-video-conf": "Napaka Zagon video klica",
"totp-invalid": "Koda ali geslo neveljavno",
"Close_Chat": "Zaprite klepet",
"Select_tags": "Izberite oznake"
"Select_tags": "Izberite oznake",
"and_N_more": "in še {{count}}"
}

View File

@ -874,5 +874,6 @@
"Call": "Ring",
"Reply_in_direct_message": "Svara med direktmeddelande",
"room_archived": "arkiverade rum",
"room_unarchived": "avarkiverade rum"
"room_unarchived": "avarkiverade rum",
"and_N_more": "och ytterligare {{count}}"
}

View File

@ -670,5 +670,6 @@
"Workspace_URL_Example": "Örn. sirketiniz.rocket.chat",
"invalid-room": "Geçersiz oda",
"Open_Livechats": "Devam Eden Sohbetler",
"Broadcast_hint": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir, ancak diğer kullanıcılar yanıt verebilir"
"Broadcast_hint": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir, ancak diğer kullanıcılar yanıt verebilir",
"and_N_more": "ve {{count}} daha"
}

View File

@ -645,5 +645,6 @@
"No_threads_following": "当前没有正在追踪的讨论",
"No_threads_unread": "当前没有未读的讨论",
"Messagebox_Send_to_channel": "发送至频道",
"Broadcast_hint": "只有经过授权的用户才能写新信息,但其他用户可以回复"
"Broadcast_hint": "只有经过授权的用户才能写新信息,但其他用户可以回复",
"and_N_more": "另外{{count}}个"
}

View File

@ -655,5 +655,6 @@
"Confirmation": "確認",
"invalid-room": "無效的房間",
"Open_Livechats": "打開即時聊天",
"Broadcast_hint": "只有經過授權的使用者才能發送新訊息,但其他使用者可以回覆"
"Broadcast_hint": "只有經過授權的使用者才能發送新訊息,但其他使用者可以回覆",
"and_N_more": "另外{{count}}個"
}

View File

@ -11,3 +11,4 @@ export * from './messageTypeLoad';
export * from './notifications';
export * from './defaultSettings';
export * from './tablet';
export * from './userAgent';

View File

@ -0,0 +1,5 @@
import { isIOS } from '../methods/helpers';
export const userAgent = isIOS
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1'
: 'Mozilla/5.0 (Linux; Android 12; SM-A315G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Mobile Safari/537.36';

View File

@ -137,5 +137,7 @@ export default class Subscription extends Model {
@field('on_hold') onHold;
@field('users_count') usersCount;
@json('source', sanitizer) source;
}

View File

@ -257,6 +257,15 @@ export default schemaMigrations({
columns: [{ name: 'e2e_suggested_key', type: 'string', isOptional: true }]
})
]
},
{
toVersion: 21,
steps: [
addColumns({
table: 'subscriptions',
columns: [{ name: 'users_count', type: 'string', isOptional: true }]
})
]
}
]
});

View File

@ -1,7 +1,7 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export default appSchema({
version: 20,
version: 21,
tables: [
tableSchema({
name: 'subscriptions',
@ -63,7 +63,8 @@ export default appSchema({
{ name: 'team_main', type: 'boolean', isOptional: true }, // Use `Q.notEq(true)` to get false or null
{ name: 'on_hold', type: 'boolean', isOptional: true },
{ name: 'source', type: 'string', isOptional: true },
{ name: 'hide_mention_status', type: 'boolean', isOptional: true }
{ name: 'hide_mention_status', type: 'boolean', isOptional: true },
{ name: 'users_count', type: 'number', isOptional: true }
]
}),
tableSchema({

View File

@ -85,6 +85,9 @@ export const merge = (
if (room && 'source' in room) {
mergedSubscription.source = room?.source;
}
if (room && 'usersCount' in room) {
mergedSubscription.usersCount = room.usersCount;
}
}
if (!mergedSubscription.name) {

View File

@ -104,6 +104,7 @@ export const search = async ({ text = '', filterUsers = true, filterRooms = true
...user,
rid: user.username,
name: user.username,
fname: user.name,
t: SubscriptionType.DIRECT,
search: true
});

View File

@ -10,15 +10,12 @@ import { OutsideModalParamList } from '../stacks/types';
import StatusBar from '../containers/StatusBar';
import ActivityIndicator from '../containers/ActivityIndicator';
import { TSupportedThemes, withTheme } from '../theme';
import { debounce, isIOS } from '../lib/methods/helpers';
import { userAgent } from '../lib/constants';
import { debounce } from '../lib/methods/helpers';
import * as HeaderButton from '../containers/HeaderButton';
import { Services } from '../lib/services';
import { IApplicationState, ICredentials } from '../definitions';
const userAgent = isIOS
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1'
: 'Mozilla/5.0 (Linux; Android 12; SM-A315G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Mobile Safari/537.36';
// iframe uses a postMessage to send the token to the client
// We'll handle this sending the token to the hash of the window.location
// https://docs.rocket.chat/guides/developer-guides/iframe-integration/authentication#iframe-url

View File

@ -1,5 +0,0 @@
import React from 'react';
declare const JitsiMeetView: React.SFC<>;
export default JitsiMeetView;

View File

@ -1,74 +0,0 @@
// @ts-ignore
// eslint-disable-next-line import/no-unresolved
import JitsiMeet from '@socialcode-rob1/react-native-jitsimeet-custom';
import React, { useEffect } from 'react';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import RCActivityIndicator from '../containers/ActivityIndicator';
import { useAppSelector } from '../lib/hooks';
import { events, logEvent } from '../lib/methods/helpers/log';
import { getUserSelector } from '../selectors/login';
import { ChatsStackParamList } from '../stacks/types';
import { endVideoConfTimer, initVideoConfTimer } from '../lib/methods/videoConfTimer';
const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLFragment: string) =>
`${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`;
const JitsiMeetView = (): React.ReactElement => {
const { goBack } = useNavigation();
const {
params: { url, onlyAudio, videoConf, rid }
} = useRoute<RouteProp<ChatsStackParamList, 'JitsiMeetView'>>();
const user = useAppSelector(state => getUserSelector(state));
const baseUrl = useAppSelector(state => state.server.server);
useEffect(() => {
initJitsi();
}, []);
const initJitsi = async () => {
const audioOnly = onlyAudio ?? false;
const { name, id: userId, token, username } = user;
const avatarAuthURLFragment = `&rc_token=${token}&rc_uid=${userId}`;
const avatar = formatUrl(username, baseUrl, 100, avatarAuthURLFragment);
const userInfo = {
displayName: name as string,
avatar
};
const regex = /(?:\/.*\/)(.*)/;
const urlWithoutServer = regex.exec(url)![1];
const serverUrl = url.replace(`/${urlWithoutServer}`, '');
const room = (url.includes('jwt=') ? urlWithoutServer.split('jwt=')[0] : urlWithoutServer.split('#')[0]).replace('?', '');
const jwtToken = url.includes('jwt=') ? url.substring(url.indexOf('jwt=') + 4, url.lastIndexOf('#config')) : undefined;
const conferenceOptions = {
room,
serverUrl,
userInfo: {
displayName: userInfo.displayName,
avatar: userInfo.avatar
},
subject: room,
audioOnly,
audioMuted: false,
videoMuted: audioOnly,
token: jwtToken,
featureFlags: {
'calendar.enabled': false
},
configOverrides: {
'breakoutRooms.hideAddRoomButton': false
}
};
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
if (!videoConf) initVideoConfTimer(rid);
await JitsiMeet.launchJitsiMeetView(conferenceOptions);
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
if (!videoConf) endVideoConfTimer();
goBack();
};
return <RCActivityIndicator absolute size='large' />;
};
export default JitsiMeetView;

View File

@ -1,18 +1,17 @@
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
import React from 'react';
import { BackHandler, NativeEventSubscription } from 'react-native';
import { isAppInstalled, openAppWithUri } from 'react-native-send-intent';
import { BackHandler, Linking, NativeEventSubscription, SafeAreaView } from 'react-native';
import WebView from 'react-native-webview';
import { WebViewMessage, WebViewNavigation } from 'react-native-webview/lib/WebViewTypes';
import { WebViewNavigation } from 'react-native-webview/lib/WebViewTypes';
import { IBaseScreen } from '../definitions';
import { userAgent } from '../lib/constants';
import { isIOS } from '../lib/methods/helpers';
import { events, logEvent } from '../lib/methods/helpers/log';
import { endVideoConfTimer, initVideoConfTimer } from '../lib/methods/videoConfTimer';
import { ChatsStackParamList } from '../stacks/types';
import { withTheme } from '../theme';
const JITSI_INTENT = 'org.jitsi.meet';
type TJitsiMeetViewProps = IBaseScreen<ChatsStackParamList, 'JitsiMeetView'>;
class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
@ -29,19 +28,8 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
}
componentDidMount() {
const { route, navigation } = this.props;
isAppInstalled(JITSI_INTENT)
.then(function (isInstalled) {
if (isInstalled) {
const callUrl = route.params.url.replace(/^https?:\/\//, '').split('#')[0];
openAppWithUri(`intent://${callUrl}#Intent;scheme=${JITSI_INTENT};package=${JITSI_INTENT};end`)
.then(() => navigation.pop())
.catch(() => {});
}
})
.catch(() => {});
this.handleJitsiApp();
this.onConferenceJoined();
this.backHandler = BackHandler.addEventListener('hardwareBackPress', () => true);
activateKeepAwake();
}
@ -50,10 +38,24 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
if (!this.videoConf) {
endVideoConfTimer();
}
this.backHandler.remove();
if (this.backHandler) {
this.backHandler.remove();
}
deactivateKeepAwake();
}
handleJitsiApp = async () => {
const { route, navigation } = this.props;
const callUrl = route.params.url.replace(/^https?:\/\//, '');
try {
await Linking.openURL(`org.jitsi.meet://${callUrl}`);
navigation.pop();
} catch (error) {
// As the jitsi app was not opened disable the backhandler on android
this.backHandler = BackHandler.addEventListener('hardwareBackPress', () => true);
}
};
// Jitsi Update Timeout needs to be called every 10 seconds to make sure
// call is not ended and is available to web users.
onConferenceJoined = () => {
@ -63,28 +65,36 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
}
};
onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => {
onNavigationStateChange = (webViewState: WebViewNavigation) => {
const { navigation, route } = this.props;
const jitsiRoomId = route.params.url
?.split(/^https?:\/\//)[1]
?.split('#')[0]
?.split('/')[1];
if ((jitsiRoomId && !webViewState.url.includes(jitsiRoomId)) || webViewState.url.includes('close')) {
navigation.pop();
if (isIOS) {
if (webViewState.navigationType) {
navigation.pop();
}
} else {
navigation.pop();
}
}
};
render() {
return (
<WebView
source={{ uri: `${this.url}${this.url.includes('#config') ? '&' : '#'}config.disableDeepLinking=true` }}
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
onNavigationStateChange={this.onNavigationStateChange}
style={{ flex: 1 }}
javaScriptEnabled
domStorageEnabled
mediaPlaybackRequiresUserAction={false}
/>
<SafeAreaView style={{ flex: 1 }}>
<WebView
source={{ uri: `${this.url}${this.url.includes('#config') ? '&' : '#'}config.disableDeepLinking=true` }}
onNavigationStateChange={this.onNavigationStateChange}
style={{ flex: 1 }}
userAgent={userAgent}
javaScriptEnabled
domStorageEnabled
mediaPlaybackRequiresUserAction={false}
/>
</SafeAreaView>
);
}
}

View File

@ -5,7 +5,6 @@ import { FlatList } from 'react-native';
import { shallowEqual } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import KeyboardView from '../../containers/KeyboardView';
import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../../containers/List';
import SafeAreaView from '../../containers/SafeAreaView';
@ -83,31 +82,29 @@ const NewMessageView = () => {
return (
<SafeAreaView testID='new-message-view'>
<KeyboardView>
<StatusBar />
<FlatList
data={search.length > 0 ? search : chats}
keyExtractor={item => item._id || item.rid}
ListHeaderComponent={<HeaderNewMessage maxUsers={maxUsers} onChangeText={handleSearch} />}
renderItem={({ item }) => {
const itemSearch = item as ISearch;
const itemModel = item as TSubscriptionModel;
<StatusBar />
<FlatList
data={search.length > 0 ? search : chats}
keyExtractor={item => item._id || item.rid}
ListHeaderComponent={<HeaderNewMessage maxUsers={maxUsers} onChangeText={handleSearch} />}
renderItem={({ item }) => {
const itemSearch = item as ISearch;
const itemModel = item as TSubscriptionModel;
return (
<UserItem
name={useRealName && itemSearch.fname ? itemSearch.fname : itemModel.name}
username={itemSearch.search ? itemSearch.username : itemModel.name}
onPress={() => goRoom(itemModel)}
testID={`new-message-view-item-${item.name}`}
/>
);
}}
ItemSeparatorComponent={List.Separator}
ListFooterComponent={List.Separator}
contentContainerStyle={{ backgroundColor: colors.backgroundColor }}
keyboardShouldPersistTaps='always'
/>
</KeyboardView>
return (
<UserItem
name={useRealName && itemSearch.fname ? itemSearch.fname : itemModel.name}
username={itemSearch.search ? itemSearch.username : itemModel.name}
onPress={() => goRoom(itemModel)}
testID={`new-message-view-item-${item.name}`}
/>
);
}}
ItemSeparatorComponent={List.Separator}
ListFooterComponent={List.Separator}
contentContainerStyle={{ backgroundColor: colors.backgroundColor }}
keyboardShouldPersistTaps='always'
/>
</SafeAreaView>
);
};

View File

@ -22,7 +22,7 @@ import { sanitizeLikeString } from '../../lib/database/utils';
import UserPreferences from '../../lib/methods/userPreferences';
import { OutsideParamList } from '../../stacks/types';
import { withTheme } from '../../theme';
import { isTablet } from '../../lib/methods/helpers';
import { isIOS, isTablet } from '../../lib/methods/helpers';
import EventEmitter from '../../lib/methods/helpers/events';
import { BASIC_AUTH_KEY, setBasicAuth } from '../../lib/methods/helpers/fetch';
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
@ -392,28 +392,32 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
style={[styles.connectButton, { marginTop: verticalScale({ size: 16, height }) }]}
testID='new-server-view-button'
/>
<OrSeparator theme={theme} />
<Text
style={[
styles.description,
{
color: themes[theme].auxiliaryText,
fontSize: moderateScale({ size: 14, width }),
marginBottom: verticalScale({ size: 16, height })
}
]}
>
{I18n.t('Onboarding_join_open_description')}
</Text>
<Button
title={I18n.t('Join_our_open_workspace')}
type='secondary'
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.connectOpen}
disabled={connecting}
loading={connectingOpen && connecting}
testID='new-server-view-open'
/>
{isIOS ? (
<>
<OrSeparator theme={theme} />
<Text
style={[
styles.description,
{
color: themes[theme].auxiliaryText,
fontSize: moderateScale({ size: 14, width }),
marginBottom: verticalScale({ size: 16, height })
}
]}
>
{I18n.t('Onboarding_join_open_description')}
</Text>
<Button
title={I18n.t('Join_our_open_workspace')}
type='secondary'
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.connectOpen}
disabled={connecting}
loading={connectingOpen && connecting}
testID='new-server-view-open'
/>
</>
) : null}
</FormContainerInner>
{this.renderCertificatePicker()}
</FormContainer>

View File

@ -86,7 +86,7 @@ interface IRoomActionsViewProps extends IActionSheetProvider, IBaseScreen<ChatsS
interface IRoomActionsViewState {
room: TSubscriptionModel;
membersCount: number;
membersCount?: number;
member: Partial<IUser>;
joined: boolean;
canViewMembers: boolean;
@ -153,10 +153,12 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
this.roomObservable = room.observe();
this.subscription = this.roomObservable.subscribe(changes => {
if (this.mounted) {
this.setState({ room: changes });
this.setState({ room: changes, membersCount: changes.usersCount });
} else {
// @ts-ignore
this.state.room = changes;
// @ts-ignore
this.state.membersCount = changes.usersCount;
}
});
}
@ -191,7 +193,8 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
try {
const counters = await Services.getRoomCounters(room.rid, room.t as any);
if (counters.success) {
this.setState({ membersCount: counters.members, joined: counters.joined });
await this.updateUsersCount(counters.members);
this.setState({ joined: counters.joined });
}
} catch (e) {
log(e);
@ -231,6 +234,23 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
return room.t === 'l' && room.status === 'queued' && !this.joined;
}
updateUsersCount = async (members: number) => {
const { room } = this.state;
if (members === room.usersCount) return;
try {
const db = database.active;
await db.write(async () => {
await room.update(
protectedFunction((r: TSubscriptionModel) => {
r.usersCount = members;
})
);
});
} catch {
//
}
};
onPressTouchable: IOnPressTouch = (item: {
route?: keyof ChatsStackParamList;
params?: ChatsStackParamList[keyof ChatsStackParamList];
@ -1052,7 +1072,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
<>
<List.Item
title='Members'
subtitle={membersCount > 0 ? `${membersCount} ${I18n.t('members')}` : undefined}
subtitle={membersCount && membersCount > 0 ? `${membersCount} ${I18n.t('members')}` : undefined}
onPress={() => this.onPressTouchable({ route: 'RoomMembersView', params: { rid, room, joined: this.joined } })}
testID='room-actions-members'
left={() => <List.Icon name='team' />}

View File

@ -906,7 +906,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
const { selectedMessage } = this.state;
this.handleCloseEmoji(showActionSheet, {
children: <ReactionsList reactions={selectedMessage?.reactions} getCustomEmoji={this.getCustomEmoji} />,
snaps: ['50%', '80%'],
snaps: ['50%'],
enableContentPanningGesture: false
});
};

View File

@ -11,7 +11,6 @@ import { Header } from '@react-navigation/elements';
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native';
import { Dispatch } from 'redux';
import KeyboardView from '../../containers/KeyboardView';
import database from '../../lib/database';
import RoomItem, { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from '../../containers/RoomItem';
import log, { logEvent, events } from '../../lib/methods/helpers/log';
@ -63,8 +62,8 @@ import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes } from
import { Services } from '../../lib/services';
type TNavigation = CompositeNavigationProp<
StackNavigationProp<ChatsStackParamList, 'RoomsListView'>,
CompositeNavigationProp<StackNavigationProp<ChatsStackParamList>, StackNavigationProp<DrawerParamList>>
StackNavigationProp<ChatsStackParamList, 'RoomsListView'>,
CompositeNavigationProp<StackNavigationProp<ChatsStackParamList>, StackNavigationProp<DrawerParamList>>
>;
interface IRoomsListViewProps {
@ -480,11 +479,11 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
internalSetState = (
state:
| ((
prevState: Readonly<IRoomsListViewState>,
props: Readonly<IRoomsListViewProps>
| ((
prevState: Readonly<IRoomsListViewState>,
props: Readonly<IRoomsListViewProps>
) => Pick<IRoomsListViewState, keyof IRoomsListViewState> | IRoomsListViewState | null)
| (Pick<IRoomsListViewState, keyof IRoomsListViewState> | IRoomsListViewState | null),
| (Pick<IRoomsListViewState, keyof IRoomsListViewState> | IRoomsListViewState | null),
callback?: () => void
) => {
if (this.animated) {
@ -908,7 +907,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
}
};
getScrollRef = (ref: FlatList) => this.scroll = ref;
getScrollRef = (ref: FlatList) => (this.scroll = ref);
renderListHeader = () => {
const { searching } = this.state;
@ -1026,11 +1025,9 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
return (
<SafeAreaView testID='rooms-list-view' style={{ backgroundColor: themes[theme].backgroundColor }}>
<KeyboardView>
<StatusBar />
{this.renderHeader()}
{this.renderScroll()}
</KeyboardView>
<StatusBar />
{this.renderHeader()}
{this.renderScroll()}
{/* TODO - this ts-ignore is here because the route props, on IBaseScreen*/}
{/* @ts-ignore*/}
{showServerDropdown ? <ServerDropdown navigation={navigation} theme={theme} /> : null}

View File

@ -39,7 +39,6 @@ import {
ICustomEmoji
} from '../../definitions';
import { Services } from '../../lib/services';
import KeyboardView from '../../containers/KeyboardView';
const QUERY_SIZE = 50;
@ -328,20 +327,18 @@ class SearchMessagesView extends React.Component<ISearchMessagesViewProps, ISear
return (
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='search-messages-view'>
<StatusBar />
<KeyboardView>
<View style={styles.searchContainer}>
<FormTextInput
autoFocus
label={I18n.t('Search')}
onChangeText={this.search}
placeholder={I18n.t('Search_Messages')}
testID='search-message-view-input'
/>
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} theme={theme} />
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
</View>
{this.renderList()}
</KeyboardView>
<View style={styles.searchContainer}>
<FormTextInput
autoFocus
label={I18n.t('Search')}
onChangeText={this.search}
placeholder={I18n.t('Search_Messages')}
testID='search-message-view-input'
/>
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} theme={theme} />
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
</View>
{this.renderList()}
</SafeAreaView>
);
}

View File

@ -35,7 +35,6 @@ import styles from './styles';
import { IApplicationState, IBaseScreen, IMessage, SubscriptionType, TSubscriptionModel, TThreadModel } from '../../definitions';
import { getUidDirectMessage, debounce, isIOS } from '../../lib/methods/helpers';
import { Services } from '../../lib/services';
import KeyboardView from '../../containers/KeyboardView';
const API_FETCH_COUNT = 50;
@ -517,18 +516,16 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
return (
<SafeAreaView testID='thread-messages-view'>
<KeyboardView>
<StatusBar />
{this.renderContent()}
{showFilterDropdown ? (
<Dropdown
currentFilter={currentFilter}
onFilterSelected={this.onFilterSelected}
onClose={this.closeFilterDropdown}
theme={theme}
/>
) : null}
</KeyboardView>
<StatusBar />
{this.renderContent()}
{showFilterDropdown ? (
<Dropdown
currentFilter={currentFilter}
onFilterSelected={this.onFilterSelected}
onClose={this.closeFilterDropdown}
theme={theme}
/>
) : null}
</SafeAreaView>
);
}

View File

@ -31,7 +31,7 @@ const data = {
return {
username: `user${randomVal}`,
name: `user${randomVal}`, // FIXME: apply a different name
password: `password${randomVal}`,
password: `Password1@${randomVal}`,
email: `mobile+${randomVal}@rocket.chat`
};
}

View File

@ -108,7 +108,10 @@ describe('E2E Encryption', () => {
});
it('should tap "How it works" and navigate', async () => {
await element(by.id('e2e-save-password-view-how-it-works').and(by.label('How It Works'))).tap();
await waitFor(element(by[textMatcher]('How It Works')).atIndex(0))
.toExist()
.withTimeout(1000);
await element(by.id('e2e-save-password-view-how-it-works')).tap();
await waitFor(element(by.id('e2e-how-it-works-view')))
.toBeVisible()
.withTimeout(2000);
@ -116,7 +119,10 @@ describe('E2E Encryption', () => {
});
it('should tap "Save my password" and close modal', async () => {
await element(by.id('e2e-save-password-view-saved-password').and(by.label('I Saved My E2E Password'))).tap();
await waitFor(element(by[textMatcher]('I Saved My E2E Password')).atIndex(0))
.toExist()
.withTimeout(1000);
await element(by.id('e2e-save-password-view-saved-password')).tap();
await sleep(300); // wait for animation
await waitFor(element(by.id('rooms-list-view')))
.toBeVisible()
@ -237,8 +243,14 @@ describe('E2E Encryption', () => {
.toBeVisible()
.withTimeout(2000);
await expect(element(by.id('e2e-encryption-security-view-password'))).toExist();
await expect(element(by.id('e2e-encryption-security-view-change-password').and(by.label('Save Changes')))).toExist();
await expect(element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key')))).toExist();
await waitFor(element(by[textMatcher]('Save Changes')).atIndex(0))
.toExist()
.withTimeout(1000);
await expect(element(by.id('e2e-encryption-security-view-change-password'))).toExist();
await waitFor(element(by[textMatcher]('Reset E2E Key')).atIndex(0))
.toExist()
.withTimeout(1000);
await expect(element(by.id('e2e-encryption-security-view-reset-key'))).toExist();
});
});
@ -330,7 +342,10 @@ describe('E2E Encryption', () => {
await waitFor(element(by.id('e2e-encryption-security-view')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key'))).tap();
await waitFor(element(by[textMatcher]('Reset E2E Key')).atIndex(0))
.toExist()
.withTimeout(1000);
await element(by.id('e2e-encryption-security-view-reset-key')).tap();
await waitFor(element(by[textMatcher]('Are you sure?')))
.toExist()
.withTimeout(2000);

View File

@ -1,9 +1,10 @@
import Detox, { device, waitFor, element, by, expect } from 'detox';
import { navigateToLogin, login } from '../../helpers/app';
import { navigateToLogin, login, TTextMatcher, platformTypes } from '../../helpers/app';
import { createRandomUser, ITestUser } from '../../helpers/data_setup';
const defaultLaunchArgs = { permissions: { notifications: 'YES' } } as Detox.DeviceLaunchAppConfig;
let textMatcher: TTextMatcher;
const navToLanguage = async () => {
await waitFor(element(by.id('rooms-list-view')))
@ -28,10 +29,13 @@ const navToLanguage = async () => {
describe('i18n', () => {
describe('OS language', () => {
beforeAll(() => {
({ textMatcher } = platformTypes[device.getPlatform()]);
});
it("OS set to 'en' and proper translate to 'en'", async () => {
// if (device.getPlatform() === 'android') {
// return; // FIXME: Passing language with launch parameters doesn't work with Android
// }
if (device.getPlatform() === 'android') {
return; // FIXME: Passing language with launch parameters doesn't work with Android
}
await device.launchApp({
...defaultLaunchArgs,
languageAndLocale: {
@ -43,13 +47,16 @@ describe('i18n', () => {
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(20000);
await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible();
await expect(element(by.id('new-server-view-open'))).toBeVisible();
await waitFor(element(by[textMatcher]('Join our open workspace')).atIndex(0))
.toExist()
.withTimeout(1000);
});
it("OS set to unavailable language and fallback to 'en'", async () => {
// if (device.getPlatform() === 'android') {
// return; // FIXME: Passing language with launch parameters doesn't work with Android
// }
if (device.getPlatform() === 'android') {
return; // FIXME: Passing language with launch parameters doesn't work with Android
}
await device.launchApp({
...defaultLaunchArgs,
languageAndLocale: {
@ -60,7 +67,10 @@ describe('i18n', () => {
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(20000);
await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible();
await expect(element(by.id('new-server-view-open'))).toBeVisible();
await waitFor(element(by[textMatcher]('Join our open workspace')).atIndex(0))
.toExist()
.withTimeout(1000);
});
/**

View File

@ -19,9 +19,9 @@ describe('Onboarding', () => {
await expect(element(by.id('new-server-view'))).toBeVisible();
});
it('should have "Join our open workspace"', async () => {
await expect(element(by.id('new-server-view-open'))).toBeVisible();
});
// it('should have "Join our open workspace"', async () => {
// await expect(element(by.id('new-server-view-open'))).toBeVisible();
// });
});
describe('Usage', () => {
@ -34,12 +34,12 @@ describe('Onboarding', () => {
await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
});
it('should tap on "Join our open workspace" and navigate', async () => {
await element(by.id('new-server-view-open')).tap();
await waitFor(element(by.id('workspace-view')))
.toBeVisible()
.withTimeout(60000);
});
// it('should tap on "Join our open workspace" and navigate', async () => {
// await element(by.id('new-server-view-open')).tap();
// await waitFor(element(by.id('workspace-view')))
// .toBeVisible()
// .withTimeout(60000);
// });
it('should enter a valid server without login services and navigate to login', async () => {
await device.launchApp({ newInstance: true });

View File

@ -103,6 +103,7 @@ describe('Room actions screen', () => {
it('should have members', async () => {
await expect(element(by.id('room-actions-members'))).toExist();
await expect(element(by[textMatcher]('1 members'))).toExist();
});
it('should have files', async () => {
@ -338,6 +339,9 @@ describe('Room actions screen', () => {
await element(by.id('selected-users-view-submit')).tap();
await sleep(300);
await backToActions();
await waitFor(element(by[textMatcher]('3 members')))
.toExist()
.withTimeout(5000);
});
describe('Room Members', () => {

View File

@ -48,6 +48,13 @@ async function waitForLoading() {
// .withTimeout(10000);
}
function getIndex() {
if (device.getPlatform() === 'android') {
return 1;
}
return 0;
}
describe('Room', () => {
beforeAll(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
@ -123,11 +130,11 @@ describe('Room', () => {
.toExist()
.withTimeout(5000);
await element(by.id('search-message-view-input')).replaceText('30');
await waitFor(element(by[textMatcher]('30')).atIndex(1))
await waitFor(element(by[textMatcher]('30')).atIndex(getIndex()))
.toExist()
.withTimeout(30000);
await sleep(1000);
await element(by[textMatcher]('30')).atIndex(1).tap();
await element(by[textMatcher]('30')).atIndex(getIndex()).tap();
await waitForLoading();
await waitFor(element(by[textMatcher]('30')).atIndex(0))
.toExist()
@ -267,10 +274,10 @@ describe('Threads', () => {
.toExist()
.withTimeout(5000);
await element(by.id('search-message-view-input')).replaceText('to be searched');
await waitFor(element(by[textMatcher]('to be searched')).atIndex(1))
await waitFor(element(by[textMatcher]('to be searched')).atIndex(getIndex()))
.toExist()
.withTimeout(30000);
await element(by[textMatcher]('to be searched')).atIndex(1).tap();
await element(by[textMatcher]('to be searched')).atIndex(getIndex()).tap();
await expectThreadMessages('to be searched');
});

View File

@ -107,9 +107,6 @@ PODS:
- GoogleUtilities/Logger
- hermes-engine (0.11.0)
- iosMath (0.9.4)
- JitsiMeetSDKLite (7.0.1-lite):
- JitsiWebRTC (~> 106.0)
- JitsiWebRTC (106.0.0)
- KeyCommands (2.0.3):
- React
- libevent (2.1.12)
@ -366,9 +363,6 @@ PODS:
- React-Core
- react-native-document-picker (8.1.2):
- React-Core
- react-native-jitsimeet-custom (2.5.0):
- JitsiMeetSDKLite (= 7.0.1-lite)
- React-Core
- react-native-mmkv-storage (0.8.0):
- MMKV (= 1.2.13)
- React-Core
@ -630,7 +624,6 @@ DEPENDENCIES:
- "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
- "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)"
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- "react-native-jitsimeet-custom (from `../node_modules/@socialcode-rob1/react-native-jitsimeet-custom`)"
- react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-notifications (from `../node_modules/react-native-notifications`)
@ -696,8 +689,6 @@ SPEC REPOS:
- GoogleUtilities
- hermes-engine
- iosMath
- JitsiMeetSDKLite
- JitsiWebRTC
- libevent
- libwebp
- MMKV
@ -784,8 +775,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-cookies/cookies"
react-native-document-picker:
:path: "../node_modules/react-native-document-picker"
react-native-jitsimeet-custom:
:path: "../node_modules/@socialcode-rob1/react-native-jitsimeet-custom"
react-native-mmkv-storage:
:path: "../node_modules/react-native-mmkv-storage"
react-native-netinfo:
@ -917,8 +906,6 @@ SPEC CHECKSUMS:
GoogleUtilities: c2bdc4cf2ce786c4d2e6b3bcfd599a25ca78f06f
hermes-engine: 84e3af1ea01dd7351ac5d8689cbbea1f9903ffc3
iosMath: f7a6cbadf9d836d2149c2a84c435b1effc244cba
JitsiMeetSDKLite: d59573336ce887ec52327a9927aa8443f560d0b9
JitsiWebRTC: f441eb0e2d67f0588bf24e21c5162e97342714fb
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
@ -946,7 +933,6 @@ SPEC CHECKSUMS:
react-native-cameraroll: 2957f2bce63ae896a848fbe0d5352c1bd4d20866
react-native-cookies: f54fcded06bb0cda05c11d86788020b43528a26c
react-native-document-picker: f5ec1a712ca2a975c233117f044817bb8393cad4
react-native-jitsimeet-custom: a57ca376bfc1c69f639b138f2de2a10e0ed42c04
react-native-mmkv-storage: 8ba3c0216a6df283ece11205b442a3e435aec4e5
react-native-netinfo: e849fc21ca2f4128a5726c801a82fc6f4a6db50d
react-native-notifications: 83b4fd4a127a6c918fc846cae90da60f84819e44

View File

@ -270,7 +270,6 @@
56A64DDF0C1730D640D3931C /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; };
60B2A6A31FC4588700BD58E5 /* RocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RocketChatRN.entitlements; path = RocketChatRN/RocketChatRN.entitlements; sourceTree = "<group>"; };
6346D438F967C4A0CF0218B4 /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; };
65360F272979AA1500778C04 /* JitsiMeetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = JitsiMeetViewController.swift; path = "../node_modules/@socialcode-rob1/react-native-jitsimeet-custom/ios/JitsiMeetViewController.swift"; sourceTree = "<group>"; };
7A006F13229C83B600803143 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
7A14FCEC257FEB3A005BDCD4 /* Experimental.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Experimental.xcassets; sourceTree = "<group>"; };
@ -356,7 +355,6 @@
13B07FAE1A68108700A75B9A /* RocketChatRN */ = {
isa = PBXGroup;
children = (
65360F272979AA1500778C04 /* JitsiMeetViewController.swift */,
60B2A6A31FC4588700BD58E5 /* RocketChatRN.entitlements */,
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
@ -940,15 +938,11 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/JitsiMeetSDKLite/JitsiMeetSDK.framework/JitsiMeetSDK",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/JitsiWebRTC/WebRTC.framework/WebRTC",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JitsiMeetSDK.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
@ -1301,15 +1295,11 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/JitsiMeetSDKLite/JitsiMeetSDK.framework/JitsiMeetSDK",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/JitsiWebRTC/WebRTC.framework/WebRTC",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JitsiMeetSDK.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
@ -1765,7 +1755,7 @@
INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 4.37.1;
MARKETING_VERSION = 4.38.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
@ -1804,7 +1794,7 @@
INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 4.37.1;
MARKETING_VERSION = 4.38.0;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;

View File

@ -26,7 +26,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.37.1</string>
<string>4.38.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@ -26,7 +26,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>4.37.1</string>
<string>4.38.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>KeychainGroup</key>

View File

@ -21,7 +21,6 @@ module.exports = {
},
maxWorkers: 2,
resolver: {
blocklistRE: blocklist([/ios\/Pods\/JitsiMeetSDK\/Frameworks\/JitsiMeet.framework\/assets\/node_modules\/react-native\/.*/]),
resolverMainFields: ['sbmodern', 'react-native', 'browser', 'main'],
sourceExts: process.env.RUNNING_E2E_TESTS ? ['mock.ts', ...defaultSourceExts] : defaultSourceExts
}

View File

@ -1,6 +1,6 @@
{
"name": "rocket-chat-reactnative",
"version": "4.37.1",
"version": "4.38.0",
"private": true,
"scripts": {
"start": "react-native start",
@ -64,7 +64,6 @@
"@rocket.chat/message-parser": "^0.31.14",
"@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile",
"@rocket.chat/ui-kit": "^0.31.19",
"@socialcode-rob1/react-native-jitsimeet-custom": "socialcode-rob1/react-native-jitsimeet-custom.git",
"bytebuffer": "^5.0.1",
"color2k": "1.2.4",
"commonmark": "git+https://github.com/RocketChat/commonmark.js.git",
@ -127,7 +126,6 @@
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "3.13.1",
"react-native-scrollable-tab-view": "ptomasroos/react-native-scrollable-tab-view",
"react-native-send-intent": "^1.3.0",
"react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.1",
"react-native-skeleton-placeholder": "^5.2.3",
"react-native-slowlog": "^1.0.2",

View File

@ -1,11 +0,0 @@
diff --git a/node_modules/@socialcode-rob1/react-native-jitsimeet-custom/react-native-jitsimeet-custom.podspec b/node_modules/@socialcode-rob1/react-native-jitsimeet-custom/react-native-jitsimeet-custom.podspec
index 80a584b..5b2f71c 100644
--- a/node_modules/@socialcode-rob1/react-native-jitsimeet-custom/react-native-jitsimeet-custom.podspec
+++ b/node_modules/@socialcode-rob1/react-native-jitsimeet-custom/react-native-jitsimeet-custom.podspec
@@ -16,5 +16,5 @@ Pod::Spec.new do |s|
s.source_files = "ios/**/*.{h,m,mm,swift}"
s.dependency "React-Core"
- s.dependency 'JitsiMeetSDK', '6.2.2'
+ s.dependency 'JitsiMeetSDKLite', '7.0.1-lite'
end

View File

@ -14,11 +14,6 @@ module.exports = {
platforms: {
android: null
}
},
'@socialcode-rob1/react-native-jitsimeet-custom': {
platforms: {
android: null
}
}
}
};

View File

@ -5431,10 +5431,6 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
"@socialcode-rob1/react-native-jitsimeet-custom@socialcode-rob1/react-native-jitsimeet-custom.git":
version "2.5.0"
resolved "https://codeload.github.com/socialcode-rob1/react-native-jitsimeet-custom/tar.gz/b1f57cd065028fef2e806824e176d2b54e12cfaf"
"@storybook/addon-storyshots@6.3":
version "6.3.13"
resolved "https://registry.yarnpkg.com/@storybook/addon-storyshots/-/addon-storyshots-6.3.13.tgz#282a48880e5074baea7b84f5b091591eb21a9485"
@ -17581,11 +17577,6 @@ react-native-scrollable-tab-view@ptomasroos/react-native-scrollable-tab-view:
prop-types "^15.6.0"
react-timer-mixin "^0.13.3"
react-native-send-intent@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/react-native-send-intent/-/react-native-send-intent-1.3.0.tgz#d8c7898827da1b8b10e25a645ce6802d1a0b440c"
integrity sha512-ODTX7BHITFxdcAL0K2iHfa3qVYnqG8GPcv1NbLBNC1DyCaOSJiiGtVH6Kc5YBqzQ8+1pV9uN5nfQ5wyFgiq74g==
react-native-simple-crypto@RocketChat/react-native-simple-crypto#0.5.1:
version "0.5.1"
resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/dcf6eef5359c739d521371918e13a73f2ea6cb42"