Merge pull request #4570 from RocketChat/beta

Merge 4.31.0 into master
This commit is contained in:
Diego Mello 2022-09-28 09:14:14 -03:00 committed by GitHub
commit 61c64a3837
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 1350 additions and 844 deletions

View File

@ -156,22 +156,6 @@ module.exports = {
__DEV__: true __DEV__: true
}, },
overrides: [ overrides: [
{
files: ['e2e/**'],
globals: {
by: true,
detox: true,
device: true,
element: true,
expect: true,
waitFor: true
},
rules: {
'import/no-extraneous-dependencies': 0,
'no-await-in-loop': 0,
'no-restricted-syntax': 0
}
},
{ {
files: ['**/*.ts', '**/*.tsx'], files: ['**/*.ts', '**/*.tsx'],
extends: [ extends: [
@ -253,6 +237,23 @@ module.exports = {
} }
} }
} }
},
{
files: ['e2e/**'],
globals: {
by: true,
detox: true,
device: true,
element: true,
waitFor: true
},
rules: {
'import/no-extraneous-dependencies': 0,
'no-await-in-loop': 0,
'no-restricted-syntax': 0,
// TODO: remove this rule when update Detox to 20 and test if the namespace Detox is available
'no-undef': 1
}
} }
] ]
}; };

1
.gitignore vendored
View File

@ -66,5 +66,6 @@ artifacts
e2e/docker/rc_test_env/docker-compose.yml e2e/docker/rc_test_env/docker-compose.yml
e2e/docker/data/db e2e/docker/data/db
e2e/e2e_account.js e2e/e2e_account.js
e2e/e2e_account.ts
*.p8 *.p8

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots SearchBox Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"value\\":\\"\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#2f343d\\"},[{\\"position\\":\\"absolute\\",\\"top\\":14},{\\"right\\":15}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`; exports[`Storyshots SearchBox Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\",\\"style\\":{\\"backgroundColor\\":\\"#ffffff\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\",\\"justifyContent\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":2,\\"borderRadius\\":2},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"value\\":\\"\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#2f343d\\"},[{\\"position\\":\\"absolute\\"},{\\"right\\":15}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,5 +1,7 @@
package chat.rocket.reactnative; package chat.rocket.reactnative;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -35,8 +37,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
public class CustomPushNotification extends PushNotification { public class CustomPushNotification extends PushNotification {
public static ReactApplicationContext reactApplicationContext; public static ReactApplicationContext reactApplicationContext;
final NotificationManager notificationManager; final NotificationManager notificationManager;
@ -322,7 +322,12 @@ public class CustomPushNotification extends PushNotification {
replyIntent.setAction(KEY_REPLY); replyIntent.setAction(KEY_REPLY);
replyIntent.putExtra("pushNotification", bundle); replyIntent.putExtra("pushNotification", bundle);
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent replyPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
} else {
replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY) RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
.setLabel(label) .setLabel(label)
@ -343,7 +348,7 @@ public class CustomPushNotification extends PushNotification {
Intent intent = new Intent(mContext, DismissNotification.class); Intent intent = new Intent(mContext, DismissNotification.class);
intent.putExtra(NOTIFICATION_ID, notificationId); intent.putExtra(NOTIFICATION_ID, notificationId);
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, 0); PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
notification.setDeleteIntent(dismissPendingIntent); notification.setDeleteIntent(dismissPendingIntent);
} }

View File

@ -13,8 +13,7 @@ buildscript {
buildToolsVersion = "31.0.0" buildToolsVersion = "31.0.0"
minSdkVersion = 23 minSdkVersion = 23
compileSdkVersion = 31 compileSdkVersion = 31
// TODO: Fix "android:exported" issue and target 31 again targetSdkVersion = 31
targetSdkVersion = 30
if (System.properties['os.arch'] == "aarch64") { if (System.properties['os.arch'] == "aarch64") {
// For M1 Users we need to use the NDK 24 which added support for aarch64 // For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = "24.0.8215888" ndkVersion = "24.0.8215888"
@ -27,8 +26,8 @@ buildscript {
kotlinVersion = '1.6.10' kotlinVersion = '1.6.10'
supportLibVersion = "28.0.0" supportLibVersion = "28.0.0"
libre_build = !(isPlay.toBoolean()) libre_build = !(isPlay.toBoolean())
jitsi_url = isPlay ? "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases" : "https://github.com/RocketChat/jitsi-maven-repository/raw/libre/releases" jitsi_url = "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases"
jitsi_version = isPlay ? "3.6.0" : "3.6.0-libre" jitsi_version = "3.7.0"
} }
repositories { repositories {

View File

@ -9,7 +9,7 @@ import { PADDING_HORIZONTAL } from './constants';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
paddingBottom: 12, paddingVertical: 8,
paddingHorizontal: PADDING_HORIZONTAL paddingHorizontal: PADDING_HORIZONTAL
}, },
title: { title: {

View File

@ -5,7 +5,7 @@ import { Header } from '.';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
marginVertical: 16 marginBottom: 16
} }
}); });

View File

@ -2,6 +2,6 @@ import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
contentContainerStyleFlatList: { contentContainerStyleFlatList: {
paddingVertical: 32 paddingVertical: 16
} }
}); });

View File

@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { StyleSheet, TextInputProps, View } from 'react-native'; import { StyleSheet, TextInputProps, View } from 'react-native';
import { useTheme } from '../../theme';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { FormTextInput } from '../TextInput'; import { FormTextInput } from '../TextInput';
@ -14,13 +15,15 @@ const styles = StyleSheet.create({
const SearchBox = ({ onChangeText, onSubmitEditing, testID }: TextInputProps): JSX.Element => { const SearchBox = ({ onChangeText, onSubmitEditing, testID }: TextInputProps): JSX.Element => {
const [text, setText] = useState(''); const [text, setText] = useState('');
const { colors } = useTheme();
const internalOnChangeText = useCallback(value => { const internalOnChangeText = useCallback(value => {
setText(value); setText(value);
onChangeText?.(value); onChangeText?.(value);
}, []); }, []);
return ( return (
<View testID='searchbox'> <View testID='searchbox' style={{ backgroundColor: colors.backgroundColor }}>
<FormTextInput <FormTextInput
autoCapitalize='none' autoCapitalize='none'
autoCorrect={false} autoCorrect={false}

View File

@ -27,7 +27,7 @@ const styles = StyleSheet.create({
height: 48, height: 48,
fontSize: 16, fontSize: 16,
padding: 14, padding: 14,
borderWidth: StyleSheet.hairlineWidth, borderWidth: 2,
borderRadius: 2 borderRadius: 2
}, },
inputIconLeft: { inputIconLeft: {
@ -37,11 +37,11 @@ const styles = StyleSheet.create({
paddingRight: 45 paddingRight: 45
}, },
wrap: { wrap: {
position: 'relative' position: 'relative',
justifyContent: 'center'
}, },
iconContainer: { iconContainer: {
position: 'absolute', position: 'absolute'
top: 14
}, },
iconLeft: { iconLeft: {
left: 15 left: 15
@ -98,7 +98,7 @@ export const FormTextInput = ({
style={[ style={[
styles.input, styles.input,
iconLeft && styles.inputIconLeft, iconLeft && styles.inputIconLeft,
(secureTextEntry || iconRight) && styles.inputIconRight, (secureTextEntry || iconRight || showClearInput) && styles.inputIconRight,
{ {
backgroundColor: colors.backgroundColor, backgroundColor: colors.backgroundColor,
borderColor: colors.separatorColor, borderColor: colors.separatorColor,

View File

@ -27,3 +27,44 @@ export const ShortAndLong = () => (
</View> </View>
</> </>
); );
export const Icons = () => (
<>
<View style={styles.paddingHorizontal}>
<FormTextInput label='Right icon' placeholder='placeholder' value={item.name} iconRight={'close'} />
<FormTextInput label='Left icon' placeholder='placeholder' value={item.longText} iconLeft={'mail'} />
<FormTextInput label='Both icons' placeholder='placeholder' value={item.longText} iconLeft={'mail'} iconRight={'add'} />
<FormTextInput
label='Icon and touchable clear input'
placeholder='placeholder'
value={item.longText}
onClearInput={() => {}}
/>
</View>
</>
);
export const Multiline = () => (
<>
<View style={styles.paddingHorizontal}>
<FormTextInput label='Multiline text' placeholder='placeholder' multiline value={`${item.name}\n\n${item.longText}\n`} />
</View>
</>
);
export const SecureTextEntry = () => (
<>
<View style={styles.paddingHorizontal}>
<FormTextInput label='Secure text disabled' placeholder='placeholder' value={item.name} />
<FormTextInput label='Secure text enabled' placeholder='placeholder' value={item.name} secureTextEntry />
</View>
</>
);
export const Loading = () => (
<>
<View style={styles.paddingHorizontal}>
<FormTextInput label='Loading false' placeholder='placeholder' value={item.name} loading={false} />
<FormTextInput label='Loading true' placeholder='placeholder' value={item.name} loading />
</View>
</>
);

View File

@ -19,7 +19,7 @@ const styles = StyleSheet.create({
input: { input: {
height: 48, height: 48,
paddingLeft: 16, paddingLeft: 16,
borderWidth: StyleSheet.hairlineWidth, borderWidth: 2,
borderRadius: 2, borderRadius: 2,
alignItems: 'center', alignItems: 'center',
flexDirection: 'row' flexDirection: 'row'

View File

@ -35,7 +35,7 @@ export default StyleSheet.create({
minHeight: 48, minHeight: 48,
paddingHorizontal: 8, paddingHorizontal: 8,
paddingBottom: 0, paddingBottom: 0,
borderWidth: StyleSheet.hairlineWidth, borderWidth: 2,
borderRadius: 2, borderRadius: 2,
alignItems: 'center', alignItems: 'center',
flexDirection: 'row' flexDirection: 'row'

View File

@ -19,7 +19,7 @@ const styles = StyleSheet.create({
viewContainer: { viewContainer: {
marginBottom: 16, marginBottom: 16,
paddingHorizontal: 16, paddingHorizontal: 16,
borderWidth: StyleSheet.hairlineWidth, borderWidth: 2,
borderRadius: 2, borderRadius: 2,
justifyContent: 'center' justifyContent: 'center'
}, },

View File

@ -57,7 +57,7 @@ const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
}; };
const Attachments: React.FC<IMessageAttachments> = React.memo( const Attachments: React.FC<IMessageAttachments> = React.memo(
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => { ({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply, id }: IMessageAttachments) => {
const { theme } = useTheme(); const { theme } = useTheme();
if (!attachments || attachments.length === 0) { if (!attachments || attachments.length === 0) {
@ -80,7 +80,15 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
if (file && file.audio_url) { if (file && file.audio_url) {
return ( return (
<Audio key={file.audio_url} file={file} getCustomEmoji={getCustomEmoji} isReply={isReply} style={style} theme={theme} /> <Audio
key={file.audio_url}
file={file}
getCustomEmoji={getCustomEmoji}
isReply={isReply}
style={style}
theme={theme}
messageId={id}
/>
); );
} }
@ -106,7 +114,7 @@ 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} messageId={id} />;
}); });
return <>{attachmentsElements}</>; return <>{attachmentsElements}</>;
}, },

View File

@ -36,6 +36,7 @@ interface IMessageAudioProps {
theme: TSupportedThemes; theme: TSupportedThemes;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
scale?: number; scale?: number;
messageId: string;
} }
interface IMessageAudioState { interface IMessageAudioState {
@ -128,7 +129,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
} }
async componentDidMount() { async componentDidMount() {
const { file } = this.props; const { file, messageId } = this.props;
const { baseUrl, user } = this.context; const { baseUrl, user } = this.context;
let url = file.audio_url; let url = file.audio_url;
@ -139,7 +140,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
this.setState({ loading: true }); this.setState({ loading: true });
try { try {
if (url) { if (url) {
const audio = await downloadAudioFile(`${url}?rc_uid=${user.id}&rc_token=${user.token}`, url); const audio = await downloadAudioFile(`${url}?rc_uid=${user.id}&rc_token=${user.token}`, url, messageId);
await this.sound.loadAsync({ uri: audio }); await this.sound.loadAsync({ uri: audio });
} }
} catch { } catch {

View File

@ -90,6 +90,7 @@ interface IMessageReply {
timeFormat?: string; timeFormat?: string;
index: number; index: number;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
messageId: string;
} }
const Title = React.memo( const Title = React.memo(
@ -201,7 +202,7 @@ const Fields = React.memo(
); );
const Reply = React.memo( const Reply = React.memo(
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => { ({ attachment, timeFormat, index, getCustomEmoji, messageId }: IMessageReply) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { theme } = useTheme(); const { theme } = useTheme();
const { baseUrl, user, jumpToMessage } = useContext(MessageContext); const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
@ -256,6 +257,7 @@ const Reply = React.memo(
timeFormat={timeFormat} timeFormat={timeFormat}
style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14, marginBottom: 8 }]} style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14, marginBottom: 8 }]}
isReply isReply
id={messageId}
/> />
<UrlImage image={attachment.thumb_url} /> <UrlImage image={attachment.thumb_url} />
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} /> <Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />

View File

@ -14,6 +14,7 @@ export interface IMessageAttachments {
isReply?: boolean; isReply?: boolean;
showAttachment?: (file: IAttachment) => void; showAttachment?: (file: IAttachment) => void;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
id: string;
} }
export interface IMessageAvatar { export interface IMessageAvatar {

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
export interface IProfileParams { export interface IProfileParams {
name: string; realname?: string;
name?: string;
username: string; username: string;
email: string | null; email: string | null;
newPassword: string; newPassword: string;

View File

@ -110,7 +110,7 @@ export interface INotificationPreferences {
enableMessageParserEarlyAdoption: boolean; enableMessageParserEarlyAdoption: boolean;
desktopNotifications: TNotifications; desktopNotifications: TNotifications;
pushNotifications: TNotifications; pushNotifications: TNotifications;
emailNotificationMode?: 'mentions' | 'nothing'; emailNotificationMode: 'mentions' | 'nothing';
language?: string; language?: string;
} }

View File

@ -1,8 +1,17 @@
import * as FileSystem from 'expo-file-system'; import * as FileSystem from 'expo-file-system';
import { sanitizeLikeString } from '../database/utils';
import { store } from '../store/auxStore'; import { store } from '../store/auxStore';
import log from './helpers/log'; import log from './helpers/log';
const sanitizeString = (value: string) => sanitizeLikeString(value.substring(value.lastIndexOf('/') + 1));
const parseFilename = (value: string) => {
const extension = value.substring(value.lastIndexOf('.') + 1);
const filename = sanitizeString(value.substring(value.lastIndexOf('/') + 1).split('.')[0]);
return `${filename}.${extension}`;
};
const ensureDirAsync = async (dir: string, intermediates = true): Promise<void> => { const ensureDirAsync = async (dir: string, intermediates = true): Promise<void> => {
const info = await FileSystem.getInfoAsync(dir); const info = await FileSystem.getInfoAsync(dir);
if (info.exists && info.isDirectory) { if (info.exists && info.isDirectory) {
@ -12,13 +21,14 @@ const ensureDirAsync = async (dir: string, intermediates = true): Promise<void>
return ensureDirAsync(dir, intermediates); return ensureDirAsync(dir, intermediates);
}; };
export const downloadAudioFile = async (url: string, fileUrl: string): Promise<string> => { export const downloadAudioFile = async (url: string, fileUrl: string, messageId: string): Promise<string> => {
let path = ''; let path = '';
try { try {
const serverUrl = store.getState().server.server; const serverUrl = store.getState().server.server;
const serverUrlParsed = serverUrl.substring(serverUrl.lastIndexOf('/') + 1); const serverUrlParsed = sanitizeString(serverUrl);
const folderPath = `${FileSystem.documentDirectory}audios/${serverUrlParsed}`; const folderPath = `${FileSystem.documentDirectory}audios/${serverUrlParsed}`;
const filePath = `${folderPath}/${fileUrl.substring(fileUrl.lastIndexOf('/') + 1)}`; const filename = `${messageId}_${parseFilename(fileUrl)}`;
const filePath = `${folderPath}/${filename}`;
await ensureDirAsync(folderPath); await ensureDirAsync(folderPath);
const file = await FileSystem.getInfoAsync(filePath); const file = await FileSystem.getInfoAsync(filePath);
if (!file.exists) { if (!file.exists) {
@ -35,7 +45,7 @@ export const downloadAudioFile = async (url: string, fileUrl: string): Promise<s
export const deleteAllAudioFiles = async (serverUrl: string): Promise<void> => { export const deleteAllAudioFiles = async (serverUrl: string): Promise<void> => {
try { try {
const serverUrlParsed = serverUrl.substring(serverUrl.lastIndexOf('/') + 1); const serverUrlParsed = sanitizeString(serverUrl);
const path = `${FileSystem.documentDirectory}audios/${serverUrlParsed}`; const path = `${FileSystem.documentDirectory}audios/${serverUrlParsed}`;
await FileSystem.deleteAsync(path); await FileSystem.deleteAsync(path);
} catch (error) { } catch (error) {

View File

@ -1,3 +1,5 @@
import { useDebouncedCallback } from 'use-debounce';
export function debounce(func: Function, wait?: number, immediate?: boolean) { export function debounce(func: Function, wait?: number, immediate?: boolean) {
let timeout: ReturnType<typeof setTimeout> | null; let timeout: ReturnType<typeof setTimeout> | null;
function _debounce(...args: any[]) { function _debounce(...args: any[]) {
@ -21,3 +23,7 @@ export function debounce(func: Function, wait?: number, immediate?: boolean) {
_debounce.stop = () => clearTimeout(timeout!); _debounce.stop = () => clearTimeout(timeout!);
return _debounce; return _debounce;
} }
export function useDebounce(func: (...args: any) => any, wait?: number): (...args: any[]) => void {
return useDebouncedCallback(func, wait || 1000);
}

View File

@ -3,12 +3,12 @@ import { Q } from '@nozbe/watermelondb';
import { sanitizeLikeString } from '../database/utils'; import { sanitizeLikeString } from '../database/utils';
import database from '../database/index'; import database from '../database/index';
import { spotlight } from '../services/restApi'; import { spotlight } from '../services/restApi';
import { ISearch, ISearchLocal, SubscriptionType, TSubscriptionModel } from '../../definitions'; import { ISearch, ISearchLocal, SubscriptionType } from '../../definitions';
import { isGroupChat } from './helpers'; import { isGroupChat } from './helpers';
let debounce: null | ((reason: string) => void) = null; let debounce: null | ((reason: string) => void) = null;
export const localSearch = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<TSubscriptionModel[]> => { export const localSearch = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<ISearchLocal[]> => {
const searchText = text.trim(); const searchText = text.trim();
const db = database.active; const db = database.active;
const likeString = sanitizeLikeString(searchText); const likeString = sanitizeLikeString(searchText);
@ -26,7 +26,17 @@ export const localSearch = async ({ text = '', filterUsers = true, filterRooms =
subscriptions = subscriptions.filter(item => item.t !== 'd' || isGroupChat(item)); subscriptions = subscriptions.filter(item => item.t !== 'd' || isGroupChat(item));
} }
const search = subscriptions.slice(0, 7); const search = subscriptions.slice(0, 7).map(item => ({
_id: item._id,
rid: item.rid,
name: item.name,
fname: item.fname,
avatarETag: item.avatarETag,
t: item.t,
encrypted: item.encrypted,
lastMessage: item.lastMessage,
status: item.status
})) as ISearchLocal[];
return search; return search;
}; };

View File

@ -150,11 +150,7 @@ const ProfileStackNavigator = () => {
> >
<ProfileStack.Screen name='ProfileView' component={ProfileView} options={ProfileView.navigationOptions} /> <ProfileStack.Screen name='ProfileView' component={ProfileView} options={ProfileView.navigationOptions} />
<ProfileStack.Screen name='UserPreferencesView' component={UserPreferencesView} /> <ProfileStack.Screen name='UserPreferencesView' component={UserPreferencesView} />
<ProfileStack.Screen <ProfileStack.Screen name='UserNotificationPrefView' component={UserNotificationPrefView} />
name='UserNotificationPrefView'
component={UserNotificationPrefView}
options={UserNotificationPrefView.navigationOptions}
/>
<ProfileStack.Screen name='PickerView' component={PickerView} options={PickerView.navigationOptions} /> <ProfileStack.Screen name='PickerView' component={PickerView} options={PickerView.navigationOptions} />
</ProfileStack.Navigator> </ProfileStack.Navigator>
); );
@ -176,7 +172,7 @@ const SettingsStackNavigator = () => {
component={E2EEncryptionSecurityView} component={E2EEncryptionSecurityView}
options={E2EEncryptionSecurityView.navigationOptions} options={E2EEncryptionSecurityView.navigationOptions}
/> />
<SettingsStack.Screen name='LanguageView' component={LanguageView} options={LanguageView.navigationOptions} /> <SettingsStack.Screen name='LanguageView' component={LanguageView} />
<SettingsStack.Screen name='ThemeView' component={ThemeView} /> <SettingsStack.Screen name='ThemeView' component={ThemeView} />
<SettingsStack.Screen name='DefaultBrowserView' component={DefaultBrowserView} /> <SettingsStack.Screen name='DefaultBrowserView' component={DefaultBrowserView} />
<SettingsStack.Screen <SettingsStack.Screen

View File

@ -175,7 +175,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
component={SettingsView} component={SettingsView}
options={props => SettingsView.navigationOptions!({ ...props, isMasterDetail: true })} options={props => SettingsView.navigationOptions!({ ...props, isMasterDetail: true })}
/> />
<ModalStack.Screen name='LanguageView' component={LanguageView} options={LanguageView.navigationOptions} /> <ModalStack.Screen name='LanguageView' component={LanguageView} />
<ModalStack.Screen name='ThemeView' component={ThemeView} /> <ModalStack.Screen name='ThemeView' component={ThemeView} />
<ModalStack.Screen name='DefaultBrowserView' component={DefaultBrowserView} /> <ModalStack.Screen name='DefaultBrowserView' component={DefaultBrowserView} />
<ModalStack.Screen <ModalStack.Screen
@ -195,11 +195,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
<ModalStack.Screen name='E2EHowItWorksView' component={E2EHowItWorksView} /> <ModalStack.Screen name='E2EHowItWorksView' component={E2EHowItWorksView} />
<ModalStack.Screen name='E2EEnterYourPasswordView' component={E2EEnterYourPasswordView} /> <ModalStack.Screen name='E2EEnterYourPasswordView' component={E2EEnterYourPasswordView} />
<ModalStack.Screen name='UserPreferencesView' component={UserPreferencesView} /> <ModalStack.Screen name='UserPreferencesView' component={UserPreferencesView} />
<ModalStack.Screen <ModalStack.Screen name='UserNotificationPrefView' component={UserNotificationPrefView} />
name='UserNotificationPrefView'
component={UserNotificationPrefView}
options={UserNotificationPrefView.navigationOptions}
/>
<ModalStack.Screen name='SecurityPrivacyView' component={SecurityPrivacyView} /> <ModalStack.Screen name='SecurityPrivacyView' component={SecurityPrivacyView} />
<ModalStack.Screen <ModalStack.Screen
name='E2EEncryptionSecurityView' name='E2EEncryptionSecurityView'

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native'; import { RouteProp } from '@react-navigation/native';
import { FlatList, View } from 'react-native'; import { FlatList } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
@ -145,14 +145,9 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
} }
}; };
renderHeader = () => { renderHeader = () => (
const { theme } = this.props; <SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='add-existing-channel-view-search' />
return ( );
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='add-existing-channel-view-search' />
</View>
);
};
isChecked = (rid: string) => { isChecked = (rid: string) => {
const { selected } = this.state; const { selected } = this.state;

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState } from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import { RouteProp } from '@react-navigation/native'; import { RouteProp } from '@react-navigation/native';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
@ -24,7 +24,7 @@ import DropdownItemHeader from './Dropdown/DropdownItemHeader';
import styles from './styles'; import styles from './styles';
import { ICannedResponse } from '../../definitions/ICannedResponse'; import { ICannedResponse } from '../../definitions/ICannedResponse';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import { getRoomTitle, getUidDirectMessage, debounce } from '../../lib/methods/helpers'; import { getRoomTitle, getUidDirectMessage, useDebounce } from '../../lib/methods/helpers';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment'; import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
import { useAppSelector } from '../../lib/hooks'; import { useAppSelector } from '../../lib/hooks';
@ -88,7 +88,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
} }
}; };
const getDepartments = debounce(async () => { const getDepartments = useDebounce(async () => {
try { try {
const res = await Services.getDepartments(); const res = await Services.getDepartments();
if (res.success) { if (res.success) {
@ -187,12 +187,9 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
} }
}, [departments, cannedResponses]); }, [departments, cannedResponses]);
const searchCallback = useCallback( const searchCallback = useDebounce(async (text = '', department = '', depId = '') => {
debounce(async (text = '', department = '', depId = '') => { await getListCannedResponse({ text, department, depId, debounced: true });
await getListCannedResponse({ text, department, depId, debounced: true }); }, 1000);
}, 1000),
[]
); // use debounce with useCallback https://stackoverflow.com/a/58594890
useEffect(() => { useEffect(() => {
getRoomFromDb(); getRoomFromDb();

View File

@ -33,7 +33,7 @@ const styles = StyleSheet.create({
marginTop: 16 marginTop: 16
}, },
containerStyle: { containerStyle: {
marginBottom: 28 marginBottom: 16
}, },
list: { list: {
width: '100%' width: '100%'
@ -53,8 +53,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 16 paddingHorizontal: 16
}, },
buttonCreate: { buttonCreate: {
marginHorizontal: 16, margin: 16
marginTop: 24
} }
}); });

View File

@ -53,7 +53,7 @@ const DefaultBrowserView = () => {
useLayoutEffect(() => { useLayoutEffect(() => {
navigation.setOptions({ navigation.setOptions({
title: I18n.t('Close_Chat') title: I18n.t('Default_browser')
}); });
}, [navigation]); }, [navigation]);

View File

@ -10,7 +10,7 @@ import ActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n'; import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import log from '../../lib/methods/helpers/log'; import log from '../../lib/methods/helpers/log';
import { debounce, isIOS } from '../../lib/methods/helpers'; import { isIOS, useDebounce } from '../../lib/methods/helpers';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../../containers/List'; import * as List from '../../containers/List';
@ -81,10 +81,10 @@ const DiscussionsView = ({ navigation, route }: IDiscussionsViewProps): React.Re
} }
}; };
const onSearchChangeText = debounce(async (text: string) => { const onSearchChangeText = useDebounce(async (text: string) => {
setIsSearching(true); setIsSearching(true);
await load(text); await load(text);
}, 300); }, 500);
const onCancelSearchPress = () => { const onCancelSearchPress = () => {
setIsSearching(false); setIsSearching(false);
@ -145,20 +145,16 @@ const DiscussionsView = ({ navigation, route }: IDiscussionsViewProps): React.Re
navigation.setOptions(options); navigation.setOptions(options);
}, [navigation, isSearching]); }, [navigation, isSearching]);
const onDiscussionPress = debounce( const onDiscussionPress = (item: TThreadModel) => {
(item: TThreadModel) => { if (item.drid && item.t) {
if (item.drid && item.t) { navigation.push('RoomView', {
navigation.push('RoomView', { rid: item.drid,
rid: item.drid, prid: item.rid,
prid: item.rid, name: item.msg,
name: item.msg, t
t });
}); }
} };
},
1000,
true
);
const renderItem = ({ item }: { item: IMessageFromServer }) => ( const renderItem = ({ item }: { item: IMessageFromServer }) => (
<Item <Item

View File

@ -0,0 +1,31 @@
import React from 'react';
import * as List from '../../containers/List';
import { useTheme } from '../../theme';
const LanguageItem = ({
item,
language,
submit
}: {
item: { value: string; label: string };
language: string;
submit: (language: string) => Promise<void>;
}) => {
const { colors } = useTheme();
const { value, label } = item;
const isSelected = language === value;
return (
<List.Item
title={label}
onPress={() => submit(value)}
testID={`language-view-${value}`}
right={() => (isSelected ? <List.Icon name='check' color={colors.tintColor} /> : null)}
translateTitle={false}
/>
);
};
export default LanguageItem;

View File

@ -1,77 +1,53 @@
import React from 'react'; import React, { useLayoutEffect } from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import RNRestart from 'react-native-restart'; import RNRestart from 'react-native-restart';
import { connect } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { useAppSelector } from '../../lib/hooks';
import { appStart } from '../../actions/app'; import { appStart } from '../../actions/app';
import { setUser } from '../../actions/login'; import { setUser } from '../../actions/login';
import { themes } from '../../lib/constants';
import * as List from '../../containers/List'; import * as List from '../../containers/List';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import { IApplicationState, IBaseScreen, IUser, RootEnum } from '../../definitions'; import { RootEnum } from '../../definitions';
import I18n, { isRTL, LANGUAGES } from '../../i18n'; import I18n, { isRTL, LANGUAGES } from '../../i18n';
import database from '../../lib/database'; import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import { SettingsStackParamList } from '../../stacks/types'; import { SettingsStackParamList } from '../../stacks/types';
import { withTheme } from '../../theme';
import { showErrorAlert } from '../../lib/methods/helpers/info'; import { showErrorAlert } from '../../lib/methods/helpers/info';
import log, { events, logEvent } from '../../lib/methods/helpers/log'; import log, { events, logEvent } from '../../lib/methods/helpers/log';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import LanguageItem from './LanguageItem';
interface ILanguageViewProps extends IBaseScreen<SettingsStackParamList, 'LanguageView'> { const LanguageView = () => {
user: IUser; const { languageDefault, id } = useAppSelector(state => ({
} languageDefault: getUserSelector(state).language,
id: getUserSelector(state).id
}));
const language = languageDefault || 'en';
interface ILanguageViewState { const dispatch = useDispatch();
language: string; const navigation = useNavigation<StackNavigationProp<SettingsStackParamList, 'LanguageView'>>();
}
class LanguageView extends React.Component<ILanguageViewProps, ILanguageViewState> { useLayoutEffect(() => {
static navigationOptions = () => ({ navigation.setOptions({
title: I18n.t('Change_Language') title: I18n.t('Change_Language')
}); });
}, [navigation]);
constructor(props: ILanguageViewProps) { const submit = async (language: string) => {
super(props); if (languageDefault === language) {
this.state = {
language: props.user ? (props.user.language as string) : 'en'
};
}
shouldComponentUpdate(nextProps: ILanguageViewProps, nextState: ILanguageViewState) {
const { language } = this.state;
const { user, theme } = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (nextState.language !== language) {
return true;
}
if (nextProps.user.language !== user.language) {
return true;
}
return false;
}
formIsChanged = (language: string) => {
const { user } = this.props;
return user.language !== language;
};
submit = async (language: string) => {
if (!this.formIsChanged(language)) {
return; return;
} }
const { dispatch, user } = this.props; const shouldRestart = isRTL(language) || isRTL(languageDefault);
const shouldRestart = isRTL(language) || isRTL(user.language);
dispatch(appStart({ root: RootEnum.ROOT_LOADING, text: I18n.t('Change_language_loading') })); dispatch(appStart({ root: RootEnum.ROOT_LOADING, text: I18n.t('Change_language_loading') }));
// shows loading for at least 300ms // shows loading for at least 300ms
await Promise.all([this.changeLanguage(language), new Promise(resolve => setTimeout(resolve, 300))]); await Promise.all([changeLanguage(language), new Promise(resolve => setTimeout(resolve, 300))]);
if (shouldRestart) { if (shouldRestart) {
await RNRestart.Restart(); await RNRestart.Restart();
@ -80,14 +56,13 @@ class LanguageView extends React.Component<ILanguageViewProps, ILanguageViewStat
} }
}; };
changeLanguage = async (language: string) => { const changeLanguage = async (language: string) => {
logEvent(events.LANG_SET_LANGUAGE); logEvent(events.LANG_SET_LANGUAGE);
const { user, dispatch } = this.props;
const params: { language?: string } = {}; const params: { language?: string } = {};
// language // language
if (user.language !== language) { if (languageDefault !== language) {
params.language = language; params.language = language;
} }
@ -99,7 +74,7 @@ class LanguageView extends React.Component<ILanguageViewProps, ILanguageViewStat
const usersCollection = serversDB.get('users'); const usersCollection = serversDB.get('users');
await serversDB.write(async () => { await serversDB.write(async () => {
try { try {
const userRecord = await usersCollection.find(user.id); const userRecord = await usersCollection.find(id);
await userRecord.update(record => { await userRecord.update(record => {
record.language = params.language; record.language = params.language;
}); });
@ -114,47 +89,20 @@ class LanguageView extends React.Component<ILanguageViewProps, ILanguageViewStat
} }
}; };
renderIcon = () => { return (
const { theme } = this.props; <SafeAreaView testID='language-view'>
return <List.Icon name='check' color={themes[theme].tintColor} />; <StatusBar />
}; <FlatList
data={LANGUAGES}
renderItem = ({ item }: { item: { value: string; label: string } }) => { keyExtractor={item => item.value}
const { value, label } = item; ListHeaderComponent={List.Separator}
const { language } = this.state; ListFooterComponent={List.Separator}
const isSelected = language === value; contentContainerStyle={List.styles.contentContainerStyleFlatList}
renderItem={({ item }) => <LanguageItem item={item} language={language} submit={submit} />}
return ( ItemSeparatorComponent={List.Separator}
<List.Item
title={label}
onPress={() => this.submit(value)}
testID={`language-view-${value}`}
right={() => (isSelected ? this.renderIcon() : null)}
translateTitle={false}
/> />
); </SafeAreaView>
}; );
};
render() { export default LanguageView;
return (
<SafeAreaView testID='language-view'>
<StatusBar />
<FlatList
data={LANGUAGES}
keyExtractor={item => item.value}
ListHeaderComponent={List.Separator}
ListFooterComponent={List.Separator}
contentContainerStyle={List.styles.contentContainerStyleFlatList}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
/>
</SafeAreaView>
);
}
}
const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state)
});
export default connect(mapStateToProps)(withTheme(LanguageView));

View File

@ -181,6 +181,7 @@ const NotificationPreferencesView = (): React.ReactElement => {
testID='notification-preference-view-alert' testID='notification-preference-view-alert'
onChangeValue={saveNotificationSettings} onChangeValue={saveNotificationSettings}
/> />
<List.Separator />
<RenderListPicker <RenderListPicker
preference='audioNotificationValue' preference='audioNotificationValue'
room={room} room={room}

View File

@ -226,7 +226,7 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
// Name // Name
if (user.name !== name) { if (user.name !== name) {
params.name = name; params.realname = name;
} }
// Username // Username
@ -295,6 +295,8 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
if (result) { if (result) {
logEvent(events.PROFILE_SAVE_CHANGES); logEvent(events.PROFILE_SAVE_CHANGES);
params.name = params.realname;
delete params.realname;
if (customFields) { if (customFields) {
dispatch(setUser({ customFields, ...params })); dispatch(setUser({ customFields, ...params }));
} else { } else {

View File

@ -64,7 +64,7 @@ export default function ActionsSection({ rid, t, joined }: IActionsSection): Rea
}; };
return ( return (
<View style={{ paddingTop: canAddUser || canInviteUser ? 16 : 0, paddingBottom: canAddUser || canInviteUser ? 8 : 0 }}> <View style={{ paddingTop: canAddUser || canInviteUser ? 16 : 0, paddingBottom: canAddUser || canInviteUser ? 16 : 0 }}>
{['c', 'p'].includes(t) && canAddUser ? ( {['c', 'p'].includes(t) && canAddUser ? (
<> <>
<List.Separator /> <List.Separator />

View File

@ -379,9 +379,7 @@ const RoomMembersView = (): React.ReactElement => {
ListHeaderComponent={ ListHeaderComponent={
<> <>
<ActionsSection joined={params.joined as boolean} rid={state.room.rid} t={state.room.t} /> <ActionsSection joined={params.joined as boolean} rid={state.room.rid} t={state.room.t} />
<View style={{ backgroundColor: colors.backgroundColor }}> <SearchBox onChangeText={text => updateState({ filter: text.trim() })} testID='room-members-view-search' />
<SearchBox onChangeText={text => updateState({ filter: text.trim() })} testID='room-members-view-search' />
</View>
</> </>
} }
ListFooterComponent={() => (state.isLoading ? <ActivityIndicator /> : null)} ListFooterComponent={() => (state.isLoading ? <ActivityIndicator /> : null)}

View File

@ -31,7 +31,7 @@ const List = ({ listRef, ...props }: IListProps) => (
keyExtractor={(item: any) => item.id} keyExtractor={(item: any) => item.id}
contentContainerStyle={styles.contentContainer} contentContainerStyle={styles.contentContainer}
style={styles.list} style={styles.list}
inverted inverted={isIOS}
removeClippedSubviews={isIOS} removeClippedSubviews={isIOS}
initialNumToRender={7} initialNumToRender={7}
onEndReachedThreshold={0.5} onEndReachedThreshold={0.5}

View File

@ -0,0 +1,40 @@
import React from 'react';
import { RefreshControl as RNRefreshControl, RefreshControlProps, StyleSheet } from 'react-native';
import { useTheme } from '../../../theme';
import { isAndroid } from '../../../lib/methods/helpers';
const style = StyleSheet.create({
container: {
flex: 1
},
inverted: {
scaleY: -1
}
});
interface IRefreshControl extends RefreshControlProps {
children: React.ReactElement;
}
const RefreshControl = ({ children, onRefresh, refreshing }: IRefreshControl): React.ReactElement => {
const { colors } = useTheme();
if (isAndroid) {
return (
<RNRefreshControl
onRefresh={onRefresh}
refreshing={refreshing}
tintColor={colors.auxiliaryText}
style={[style.container, style.inverted]}
>
{children}
</RNRefreshControl>
);
}
const refreshControl = <RNRefreshControl onRefresh={onRefresh} refreshing={refreshing} tintColor={colors.auxiliaryText} />;
return React.cloneElement(children, { refreshControl });
};
export default RefreshControl;

View File

@ -2,12 +2,10 @@ import { Q } from '@nozbe/watermelondb';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import moment from 'moment'; import moment from 'moment';
import React from 'react'; import React from 'react';
import { FlatListProps, RefreshControl, ViewToken } from 'react-native'; import { FlatListProps, View, ViewToken, StyleSheet, Platform } from 'react-native';
import { event, Value } from 'react-native-reanimated'; import { event, Value } from 'react-native-reanimated';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { TSupportedThemes } from '../../../theme';
import { themes } from '../../../lib/constants';
import ActivityIndicator from '../../../containers/ActivityIndicator'; import ActivityIndicator from '../../../containers/ActivityIndicator';
import { TAnyMessageModel, TMessageModel, TThreadMessageModel, TThreadModel } from '../../../definitions'; import { TAnyMessageModel, TMessageModel, TThreadMessageModel, TThreadModel } from '../../../definitions';
import database from '../../../lib/database'; import database from '../../../lib/database';
@ -19,9 +17,20 @@ import List, { IListProps, TListRef } from './List';
import NavBottomFAB from './NavBottomFAB'; import NavBottomFAB from './NavBottomFAB';
import { loadMissedMessages, loadThreadMessages } from '../../../lib/methods'; import { loadMissedMessages, loadThreadMessages } from '../../../lib/methods';
import { Services } from '../../../lib/services'; import { Services } from '../../../lib/services';
import RefreshControl from './RefreshControl';
const QUERY_SIZE = 50; const QUERY_SIZE = 50;
const styles = StyleSheet.create({
inverted: {
...Platform.select({
android: {
scaleY: -1
}
})
}
});
const onScroll = ({ y }: { y: Value<number> }) => const onScroll = ({ y }: { y: Value<number> }) =>
event( event(
[ [
@ -40,7 +49,6 @@ export interface IListContainerProps {
renderRow: Function; renderRow: Function;
rid: string; rid: string;
tmid?: string; tmid?: string;
theme: TSupportedThemes;
loading: boolean; loading: boolean;
listRef: TListRef; listRef: TListRef;
hideSystemMessages?: string[]; hideSystemMessages?: string[];
@ -98,10 +106,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
shouldComponentUpdate(nextProps: IListContainerProps, nextState: IListContainerState) { shouldComponentUpdate(nextProps: IListContainerProps, nextState: IListContainerState) {
const { refreshing, highlightedMessage } = this.state; const { refreshing, highlightedMessage } = this.state;
const { hideSystemMessages, theme, tunread, ignored, loading } = this.props; const { hideSystemMessages, tunread, ignored, loading } = this.props;
if (theme !== nextProps.theme) {
return true;
}
if (loading !== nextProps.loading) { if (loading !== nextProps.loading) {
return true; return true;
} }
@ -348,7 +353,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
renderItem: FlatListProps<any>['renderItem'] = ({ item, index }) => { renderItem: FlatListProps<any>['renderItem'] = ({ item, index }) => {
const { messages, highlightedMessage } = this.state; const { messages, highlightedMessage } = this.state;
const { renderRow } = this.props; const { renderRow } = this.props;
return renderRow(item, messages[index + 1], highlightedMessage); return <View style={styles.inverted}>{renderRow(item, messages[index + 1], highlightedMessage)}</View>;
}; };
onViewableItemsChanged: FlatListProps<any>['onViewableItemsChanged'] = ({ viewableItems }) => { onViewableItemsChanged: FlatListProps<any>['onViewableItemsChanged'] = ({ viewableItems }) => {
@ -359,25 +364,23 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
console.count(`${this.constructor.name}.render calls`); console.count(`${this.constructor.name}.render calls`);
const { rid, tmid, listRef } = this.props; const { rid, tmid, listRef } = this.props;
const { messages, refreshing } = this.state; const { messages, refreshing } = this.state;
const { theme } = this.props;
return ( return (
<> <>
<EmptyRoom rid={rid} length={messages.length} mounted={this.mounted} /> <EmptyRoom rid={rid} length={messages.length} mounted={this.mounted} />
<List <RefreshControl refreshing={refreshing} onRefresh={this.onRefresh}>
onScroll={this.onScroll} <List
scrollEventThrottle={16} onScroll={this.onScroll}
listRef={listRef} scrollEventThrottle={16}
data={messages} listRef={listRef}
renderItem={this.renderItem} data={messages}
onEndReached={this.onEndReached} renderItem={this.renderItem}
ListFooterComponent={this.renderFooter} onEndReached={this.onEndReached}
onScrollToIndexFailed={this.handleScrollToIndexFailed} ListFooterComponent={this.renderFooter}
onViewableItemsChanged={this.onViewableItemsChanged} onScrollToIndexFailed={this.handleScrollToIndexFailed}
viewabilityConfig={this.viewabilityConfig} onViewableItemsChanged={this.onViewableItemsChanged}
refreshControl={ viewabilityConfig={this.viewabilityConfig}
<RefreshControl refreshing={refreshing} onRefresh={this.onRefresh} tintColor={themes[theme].auxiliaryText} /> />
} </RefreshControl>
/>
<NavBottomFAB y={this.y} onPress={this.jumpToBottom} isThread={!!tmid} /> <NavBottomFAB y={this.y} onPress={this.jumpToBottom} isThread={!!tmid} />
</> </>
); );

View File

@ -1365,8 +1365,8 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
if (showUnreadSeparator || dateSeparator) { if (showUnreadSeparator || dateSeparator) {
return ( return (
<> <>
{content}
<Separator ts={dateSeparator} unread={showUnreadSeparator} /> <Separator ts={dateSeparator} unread={showUnreadSeparator} />
{content}
</> </>
); );
} }
@ -1514,7 +1514,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
listRef={this.flatList} listRef={this.flatList}
rid={rid} rid={rid}
tmid={this.tmid} tmid={this.tmid}
theme={theme}
tunread={tunread} tunread={tunread}
ignored={ignored} ignored={ignored}
renderRow={this.renderItem} renderRow={this.renderItem}

View File

@ -107,14 +107,7 @@ class SelectListView extends React.Component<ISelectListViewProps, ISelectListVi
); );
}; };
renderSearch = () => { renderSearch = () => <SearchBox onChangeText={(text: string) => this.search(text)} testID='select-list-view-search' />;
const { theme } = this.props;
return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox onChangeText={(text: string) => this.search(text)} testID='select-list-view-search' />
</View>
);
};
search = async (text: string) => { search = async (text: string) => {
try { try {

View File

@ -44,12 +44,11 @@ const STATUS: IStatus[] = [
const styles = StyleSheet.create({ const styles = StyleSheet.create({
inputContainer: { inputContainer: {
marginTop: 32, marginTop: 16,
marginBottom: 32 marginBottom: 16
}, },
inputLeft: { inputLeft: {
position: 'absolute', position: 'absolute',
top: 12,
left: 12 left: 12
}, },
inputStyle: { inputStyle: {

View File

@ -1,17 +1,16 @@
import React, { useLayoutEffect } from 'react'; import React, { useLayoutEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
import { SettingsStackParamList } from '../stacks/types';
import I18n from '../i18n';
import { useTheme } from '../theme';
import StatusBar from '../containers/StatusBar';
import * as List from '../containers/List'; import * as List from '../containers/List';
import { supportSystemTheme } from '../lib/methods/helpers';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import UserPreferences from '../lib/methods/userPreferences'; import StatusBar from '../containers/StatusBar';
import { IThemePreference, TDarkLevel, TThemeMode } from '../definitions/ITheme';
import I18n from '../i18n';
import { THEME_PREFERENCES_KEY } from '../lib/constants';
import { supportSystemTheme } from '../lib/methods/helpers';
import { events, logEvent } from '../lib/methods/helpers/log'; import { events, logEvent } from '../lib/methods/helpers/log';
import { IThemePreference, TThemeMode, TDarkLevel } from '../definitions/ITheme'; import UserPreferences from '../lib/methods/userPreferences';
import { THEME_PREFERENCES_KEY, themes } from '../lib/constants'; import { useTheme } from '../theme';
import { IBaseScreen } from '../definitions';
const THEME_GROUP = 'THEME_GROUP'; const THEME_GROUP = 'THEME_GROUP';
const DARK_GROUP = 'DARK_GROUP'; const DARK_GROUP = 'DARK_GROUP';
@ -58,16 +57,40 @@ interface ITheme {
group: string; group: string;
} }
type IThemeViewProps = IBaseScreen<SettingsStackParamList, 'ThemeView'>; const Item = ({
onPress,
label,
value,
isSelected
}: {
onPress: () => void;
label: string;
value: string;
isSelected: boolean;
}) => {
const { colors } = useTheme();
return (
<>
<List.Item
title={label}
onPress={onPress}
testID={`theme-view-${value}`}
right={() => (isSelected ? <List.Icon name='check' color={colors.tintColor} /> : null)}
/>
<List.Separator />
</>
);
};
const ThemeView = ({ navigation }: IThemeViewProps): React.ReactElement => { const ThemeView = (): React.ReactElement => {
const { theme, themePreferences, setTheme } = useTheme(); const { themePreferences, setTheme } = useTheme();
const { setOptions } = useNavigation();
useLayoutEffect(() => { useLayoutEffect(() => {
navigation.setOptions({ setOptions({
title: I18n.t('Theme') title: I18n.t('Theme')
}); });
}, [navigation]); }, []);
const isSelected = (item: ITheme) => { const isSelected = (item: ITheme) => {
const { group } = item; const { group } = item;
@ -92,10 +115,10 @@ const ThemeView = ({ navigation }: IThemeViewProps): React.ReactElement => {
logEvent(events.THEME_SET_DARK_LEVEL, { dark_level: value }); logEvent(events.THEME_SET_DARK_LEVEL, { dark_level: value });
changes = { darkLevel: value as TDarkLevel }; changes = { darkLevel: value as TDarkLevel };
} }
_setTheme(changes); handleTheme(changes);
}; };
const _setTheme = (theme: Partial<IThemePreference>) => { const handleTheme = (theme: Partial<IThemePreference>) => {
const newTheme: IThemePreference = { ...(themePreferences as IThemePreference), ...theme }; const newTheme: IThemePreference = { ...(themePreferences as IThemePreference), ...theme };
if (setTheme) { if (setTheme) {
setTheme(newTheme); setTheme(newTheme);
@ -103,34 +126,37 @@ const ThemeView = ({ navigation }: IThemeViewProps): React.ReactElement => {
} }
}; };
const renderIcon = () => <List.Icon name='check' color={themes[theme].tintColor} />;
const renderItem = ({ item }: { item: ITheme }) => {
const { label, value } = item;
return (
<>
<List.Item
title={label}
onPress={() => onClick(item)}
testID={`theme-view-${value}`}
right={() => (isSelected(item) ? renderIcon() : null)}
/>
<List.Separator />
</>
);
};
return ( return (
<SafeAreaView testID='theme-view'> <SafeAreaView testID='theme-view'>
<StatusBar /> <StatusBar />
<List.Container> <List.Container>
<List.Section title='Theme'> <List.Section title='Theme'>
<List.Separator /> <List.Separator />
<>{themeGroup.map(item => renderItem({ item }))}</> <>
{themeGroup.map(theme => (
<Item
onPress={() => onClick(theme)}
label={theme.label}
value={theme.value}
isSelected={!!isSelected(theme)}
key={theme.label}
/>
))}
</>
</List.Section> </List.Section>
<List.Section title='Dark_level'> <List.Section title='Dark_level'>
<List.Separator /> <List.Separator />
<>{darkGroup.map(item => renderItem({ item }))}</> <>
{darkGroup.map(theme => (
<Item
onPress={() => onClick(theme)}
label={theme.label}
value={theme.value}
isSelected={!!isSelected(theme)}
key={theme.label}
/>
))}
</>
</List.Section> </List.Section>
</List.Container> </List.Container>
</SafeAreaView> </SafeAreaView>

View File

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

View File

@ -1,177 +1,114 @@
import React from 'react'; import React, { useEffect, useLayoutEffect, useState } from 'react';
import { StyleSheet, Text } from 'react-native'; import { StackNavigationProp } from '@react-navigation/stack';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { useNavigation } from '@react-navigation/native';
import { connect } from 'react-redux';
import { themes } from '../../lib/constants';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import * as List from '../../containers/List'; import * as List from '../../containers/List';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { TSupportedThemes, withTheme } from '../../theme';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import ActivityIndicator from '../../containers/ActivityIndicator'; import ActivityIndicator from '../../containers/ActivityIndicator';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import sharedStyles from '../Styles';
import { OPTIONS } from './options';
import { ProfileStackParamList } from '../../stacks/types'; import { ProfileStackParamList } from '../../stacks/types';
import { IApplicationState, INotificationPreferences, IUser } from '../../definitions'; import { INotificationPreferences } from '../../definitions';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { useAppSelector } from '../../lib/hooks';
import ListPicker from './ListPicker';
import log from '../../lib/methods/helpers/log';
const styles = StyleSheet.create({ const UserNotificationPreferencesView = () => {
pickerText: { const [preferences, setPreferences] = useState({} as INotificationPreferences);
...sharedStyles.textRegular, const [loading, setLoading] = useState(false);
fontSize: 16
}
});
type TKey = 'desktopNotifications' | 'pushNotifications' | 'emailNotificationMode'; const navigation = useNavigation<StackNavigationProp<ProfileStackParamList, 'UserNotificationPrefView'>>();
const userId = useAppSelector(state => getUserSelector(state).id);
interface IUserNotificationPreferencesViewState { useLayoutEffect(() => {
preferences: INotificationPreferences; navigation.setOptions({
loading: boolean; title: I18n.t('Notification_Preferences')
}
interface IUserNotificationPreferencesViewProps {
navigation: StackNavigationProp<ProfileStackParamList, 'UserNotificationPrefView'>;
theme: TSupportedThemes;
user: IUser;
}
class UserNotificationPreferencesView extends React.Component<
IUserNotificationPreferencesViewProps,
IUserNotificationPreferencesViewState
> {
static navigationOptions = (): StackNavigationOptions => ({
title: I18n.t('Notification_Preferences')
});
constructor(props: IUserNotificationPreferencesViewProps) {
super(props);
this.state = {
preferences: {} as INotificationPreferences,
loading: false
};
}
async componentDidMount() {
const { user } = this.props;
const { id } = user;
const result = await Services.getUserPreferences(id);
if (result.success) {
const { preferences } = result;
this.setState({ preferences, loading: true });
}
}
findDefaultOption = (key: TKey) => {
const { preferences } = this.state;
const option = preferences[key] ? OPTIONS[key].find(item => item.value === preferences[key]) : OPTIONS[key][0];
return option;
};
renderPickerOption = (key: TKey) => {
const { theme } = this.props;
const text = this.findDefaultOption(key);
return (
<Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>
{text?.label ? I18n.t(text?.label) : text?.label}
</Text>
);
};
pickerSelection = (title: string, key: TKey) => {
const { preferences } = this.state;
const { navigation } = this.props;
let values = OPTIONS[key];
const defaultOption = this.findDefaultOption(key);
if (OPTIONS[key][0]?.value !== 'default') {
const defaultValue = {
label: `${I18n.t('Default')} (${defaultOption?.label ? I18n.t(defaultOption?.label) : defaultOption?.label})`
} as {
label: string;
value: string;
};
values = [defaultValue, ...OPTIONS[key]];
}
navigation.navigate('PickerView', {
title,
data: values,
value: preferences[key],
onChangeValue: (value: string) => this.onValueChangePicker(key, value ?? defaultOption?.value)
}); });
}; }, [navigation]);
onValueChangePicker = (key: TKey, value: string) => this.saveNotificationPreferences({ [key]: value.toString() }); useEffect(() => {
async function getPreferences() {
try {
const result = await Services.getUserPreferences(userId);
if (result.success) {
setLoading(true);
setPreferences(result.preferences);
}
} catch (error) {
log(error);
}
}
getPreferences();
}, [userId]);
saveNotificationPreferences = async (params: { [key: string]: string }) => { const onValueChangePicker = async (param: { [key: string]: string }, onError: () => void) => {
const { user } = this.props; try {
const { id } = user; const result = await Services.setUserPreferences(userId, param);
const result = await Services.setUserPreferences(id, params); if (result.success) {
if (result.success) { const {
const { user: { settings }
user: { settings } } = result;
} = result; setPreferences(settings.preferences);
this.setState({ preferences: settings.preferences }); }
} catch (error) {
log(error);
onError();
} }
}; };
render() { return (
const { loading } = this.state; <SafeAreaView testID='user-notification-preference-view'>
return ( <StatusBar />
<SafeAreaView testID='user-notification-preference-view'> <List.Container>
<StatusBar /> {loading ? (
<List.Container> <>
{loading ? ( <List.Section title='Desktop_Notifications'>
<> <List.Separator />
<List.Section title='Desktop_Notifications'> <ListPicker
<List.Separator /> onChangeValue={onValueChangePicker}
<List.Item preference={'desktopNotifications'}
title='Alert' title='Alert'
testID='user-notification-preference-view-alert' testID='user-notification-preference-view-alert'
onPress={(title: string) => this.pickerSelection(title, 'desktopNotifications')} value={preferences.desktopNotifications}
right={() => this.renderPickerOption('desktopNotifications')} />
/> <List.Separator />
<List.Separator /> <List.Info info='Desktop_Alert_info' />
<List.Info info='Desktop_Alert_info' /> </List.Section>
</List.Section>
<List.Section title='Push_Notifications'> <List.Section title='Push_Notifications'>
<List.Separator /> <List.Separator />
<List.Item <ListPicker
title='Alert' onChangeValue={onValueChangePicker}
testID='user-notification-preference-view-push-notification' preference={'pushNotifications'}
onPress={(title: string) => this.pickerSelection(title, 'pushNotifications')} title='Alert'
right={() => this.renderPickerOption('pushNotifications')} testID='user-notification-preference-view-push-notification'
/> value={preferences.pushNotifications}
<List.Separator /> />
<List.Info info='Push_Notifications_Alert_Info' /> <List.Separator />
</List.Section> <List.Info info='Push_Notifications_Alert_Info' />
</List.Section>
<List.Section title='Email'> <List.Section title='Email'>
<List.Separator /> <List.Separator />
<List.Item <ListPicker
title='Alert' onChangeValue={onValueChangePicker}
testID='user-notification-preference-view-email-alert' preference={'emailNotificationMode'}
onPress={(title: string) => this.pickerSelection(title, 'emailNotificationMode')} title='Alert'
right={() => this.renderPickerOption('emailNotificationMode')} testID='user-notification-preference-view-email-alert'
/> value={preferences.emailNotificationMode}
<List.Separator /> />
<List.Info info='You_need_to_verifiy_your_email_address_to_get_notications' /> <List.Separator />
</List.Section> <List.Info info='You_need_to_verifiy_your_email_address_to_get_notications' />
</> </List.Section>
) : ( </>
<ActivityIndicator /> ) : (
)} <ActivityIndicator />
</List.Container> )}
</SafeAreaView> </List.Container>
); </SafeAreaView>
} );
} };
const mapStateToProps = (state: IApplicationState) => ({ export default UserNotificationPreferencesView;
user: getUserSelector(state)
});
export default connect(mapStateToProps)(withTheme(UserNotificationPreferencesView));

View File

@ -2,5 +2,7 @@
"timeout": 300000, "timeout": 300000,
"recursive": true, "recursive": true,
"bail": true, "bail": true,
"file": "e2e/tests/init.js" "require": ["ts-node/register"],
"file": "e2e/tests/init.ts",
"extension": ["ts"]
} }

View File

@ -26,9 +26,9 @@ Or
### 2. Prepare test data ### 2. Prepare test data
* If you're running your own Rocket.Chat server, ensure it's started (e.g. `meteor npm start` in the server project directory). * If you're running your own Rocket.Chat server, ensure it's started (e.g. `meteor npm start` in the server project directory).
* Edit `e2e/data.js`: * Edit `e2e/data.ts`:
* Set the `server` to the address of the server under test * Set the `server` to the address of the server under test
* Create a file called `e2e_account.js`, in the same folder as `data.js`. Set the `adminUser` and `adminPassword` to an admin user on that environment (or a user with at least `create-user` and `create-c` permissions). The example of how to create this file is on `e2e/e2e_account.example.js` * Create a file called `e2e_account.ts`, in the same folder as `data.ts`. Set the `adminUser` and `adminPassword` to an admin user on that environment (or a user with at least `create-user` and `create-c` permissions). The example of how to create this file is on `e2e/e2e_account.example.ts`
* Working example configs exist in `./e2e/data/`. Setting `FORCE_DEFAULT_DOCKER_DATA` to `1` in the `runTestsInDocker.sh` script will use the example config automatically * Working example configs exist in `./e2e/data/`. Setting `FORCE_DEFAULT_DOCKER_DATA` to `1` in the `runTestsInDocker.sh` script will use the example config automatically
### 3. Running tests ### 3. Running tests

View File

@ -1,8 +1,22 @@
const random = require('./helpers/random'); /* eslint-disable import/extensions, import/no-unresolved */
// eslint-disable-next-line import/no-unresolved, import/extensions import random from './helpers/random';
const account = require('./e2e_account'); // @ts-ignore
import account from './e2e_account';
const value = random(20); export interface IUser {
username: string;
password: string;
email: string;
}
export type TData = typeof data;
export type TDataKeys = keyof TData;
export type TDataUsers = keyof typeof data.users;
export type TDataChannels = keyof typeof data.channels;
export type TDataGroups = keyof typeof data.groups;
export type TDataTeams = keyof typeof data.teams;
const value: string = random(20);
const data = { const data = {
server: 'https://mobile.rocket.chat', server: 'https://mobile.rocket.chat',
...account, ...account,
@ -77,4 +91,5 @@ const data = {
}, },
random: value random: value
}; };
module.exports = data;
export default data;

View File

@ -1,7 +1,21 @@
// eslint-disable-next-line import/no-unresolved, import/extensions /* eslint-disable import/extensions, import/no-unresolved */
const random = require('./helpers/random'); // @ts-ignore
// eslint-disable-next-line import/no-unresolved, import/extensions import random from './helpers/random';
const account = require('./e2e_account'); // @ts-ignore
import account from './e2e_account';
export interface IUser {
username: string;
password: string;
email: string;
}
export type TData = typeof data;
export type TDataKeys = keyof TData;
export type TDataUsers = keyof typeof data.users;
export type TDataChannels = keyof typeof data.channels;
export type TDataGroups = keyof typeof data.groups;
export type TDataTeams = keyof typeof data.teams;
const value = random(20); const value = random(20);
const data = { const data = {
@ -72,4 +86,5 @@ const data = {
}, },
random: value random: value
}; };
module.exports = data;
export default data;

View File

@ -1,5 +1,19 @@
// eslint-disable-next-line import/no-unresolved, import/extensions /* eslint-disable import/extensions, import/no-unresolved */
const random = require('./helpers/random'); // @ts-ignore
import random from './helpers/random';
export interface IUser {
username: string;
password: string;
email: string;
}
export type TData = typeof data;
export type TDataKeys = keyof TData;
export type TDataUsers = keyof typeof data.users;
export type TDataChannels = keyof typeof data.channels;
export type TDataGroups = keyof typeof data.groups;
export type TDataTeams = keyof typeof data.teams;
const value = random(20); const value = random(20);
const data = { const data = {
@ -77,4 +91,5 @@ const data = {
}, },
random: value random: value
}; };
module.exports = data;
export default data;

View File

@ -3,4 +3,4 @@ const account = {
adminPassword: 'Change_here' adminPassword: 'Change_here'
}; };
module.exports = account; export default account;

View File

@ -1,5 +1,10 @@
const { exec } = require('child_process'); import { exec } from 'child_process';
const data = require('../data');
import { by, expect, element } from 'detox';
import data from '../data';
export type TTextMatcher = keyof Pick<Detox.ByFacade, 'text' | 'label'>;
const platformTypes = { const platformTypes = {
android: { android: {
@ -7,18 +12,18 @@ const platformTypes = {
alertButtonType: 'android.widget.Button', alertButtonType: 'android.widget.Button',
scrollViewType: 'android.widget.ScrollView', scrollViewType: 'android.widget.ScrollView',
textInputType: 'android.widget.EditText', textInputType: 'android.widget.EditText',
textMatcher: 'text' textMatcher: 'text' as TTextMatcher
}, },
ios: { ios: {
// iOS types // iOS types
alertButtonType: '_UIAlertControllerActionView', alertButtonType: '_UIAlertControllerActionView',
scrollViewType: 'UIScrollView', scrollViewType: 'UIScrollView',
textInputType: '_UIAlertControllerTextField', textInputType: '_UIAlertControllerTextField',
textMatcher: 'label' textMatcher: 'label' as TTextMatcher
} }
}; };
function sleep(ms) { function sleep(ms: number) {
return new Promise(res => setTimeout(res, ms)); return new Promise(res => setTimeout(res, ms));
} }
@ -34,7 +39,7 @@ async function navigateToWorkspace(server = data.server) {
await expect(element(by.id('workspace-view'))).toBeVisible(); await expect(element(by.id('workspace-view'))).toBeVisible();
} }
async function navigateToLogin(server) { async function navigateToLogin(server?: string) {
await navigateToWorkspace(server); await navigateToWorkspace(server);
await element(by.id('workspace-view-login')).tap(); await element(by.id('workspace-view-login')).tap();
await waitFor(element(by.id('login-view'))) await waitFor(element(by.id('login-view')))
@ -42,7 +47,7 @@ async function navigateToLogin(server) {
.withTimeout(2000); .withTimeout(2000);
} }
async function navigateToRegister(server) { async function navigateToRegister(server?: string) {
await navigateToWorkspace(server); await navigateToWorkspace(server);
await element(by.id('workspace-view-register')).tap(); await element(by.id('workspace-view-register')).tap();
await waitFor(element(by.id('register-view'))) await waitFor(element(by.id('register-view')))
@ -50,7 +55,7 @@ async function navigateToRegister(server) {
.withTimeout(2000); .withTimeout(2000);
} }
async function login(username, password) { async function login(username: string, password: string) {
await waitFor(element(by.id('login-view'))) await waitFor(element(by.id('login-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
@ -90,23 +95,22 @@ async function logout() {
await expect(element(by.id('new-server-view'))).toBeVisible(); await expect(element(by.id('new-server-view'))).toBeVisible();
} }
async function mockMessage(message, isThread = false) { async function mockMessage(message: string, isThread = false) {
const deviceType = device.getPlatform(); const deviceType = device.getPlatform();
const { textMatcher } = platformTypes[deviceType]; const { textMatcher } = platformTypes[deviceType];
const input = isThread ? 'messagebox-input-thread' : 'messagebox-input'; const input = isThread ? 'messagebox-input-thread' : 'messagebox-input';
await element(by.id(input)).replaceText(`${data.random}${message}`); await element(by.id(input)).replaceText(`${data.random}${message}`);
await sleep(300); await sleep(300);
await element(by.id('messagebox-send-message')).tap(); await element(by.id('messagebox-send-message')).tap();
await sleep(500);
await waitFor(element(by[textMatcher](`${data.random}${message}`))) await waitFor(element(by[textMatcher](`${data.random}${message}`)))
.toExist() .toExist()
.withTimeout(10000); .withTimeout(60000);
await element(by[textMatcher](`${data.random}${message}`)) await element(by[textMatcher](`${data.random}${message}`))
.atIndex(0) .atIndex(0)
.tap(); .tap();
} }
async function starMessage(message) { async function starMessage(message: string) {
const deviceType = device.getPlatform(); const deviceType = device.getPlatform();
const { textMatcher } = platformTypes[deviceType]; const { textMatcher } = platformTypes[deviceType];
const messageLabel = `${data.random}${message}`; const messageLabel = `${data.random}${message}`;
@ -120,7 +124,7 @@ async function starMessage(message) {
.withTimeout(5000); .withTimeout(5000);
} }
async function pinMessage(message) { async function pinMessage(message: string) {
const deviceType = device.getPlatform(); const deviceType = device.getPlatform();
const { textMatcher } = platformTypes[deviceType]; const { textMatcher } = platformTypes[deviceType];
const messageLabel = `${data.random}${message}`; const messageLabel = `${data.random}${message}`;
@ -148,24 +152,25 @@ async function tapBack() {
await element(by.id('header-back')).atIndex(0).tap(); await element(by.id('header-back')).atIndex(0).tap();
} }
async function searchRoom(room) { async function searchRoom(room: string) {
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
.withTimeout(30000); .withTimeout(30000);
await element(by.id('rooms-list-view-search')).tap(); await element(by.id('rooms-list-view-search')).tap();
await expect(element(by.id('rooms-list-view-search-input'))).toExist();
await waitFor(element(by.id('rooms-list-view-search-input'))) await waitFor(element(by.id('rooms-list-view-search-input')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await expect(element(by.id('rooms-list-view-search-input'))).toExist();
await sleep(300); await sleep(300);
await element(by.id('rooms-list-view-search-input')).replaceText(room); await element(by.id('rooms-list-view-search-input')).typeText(room);
await sleep(300); await sleep(300);
await waitFor(element(by.id(`rooms-list-view-item-${room}`))) await waitFor(element(by.id(`rooms-list-view-item-${room}`)))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);
} }
async function tryTapping(theElement, timeout, longtap = false) { // eslint-disable-next-line no-undef
async function tryTapping(theElement: Detox.IndexableNativeElement, timeout: number, longtap = false) {
try { try {
if (longtap) { if (longtap) {
await theElement.longPress(); await theElement.longPress();
@ -182,7 +187,7 @@ async function tryTapping(theElement, timeout, longtap = false) {
} }
} }
const checkServer = async server => { const checkServer = async (server: string) => {
const label = `Connected to ${server}`; const label = `Connected to ${server}`;
await element(by.id('rooms-list-view-sidebar')).tap(); await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar-view'))) await waitFor(element(by.id('sidebar-view')))
@ -200,9 +205,9 @@ const checkServer = async server => {
.withTimeout(10000); .withTimeout(10000);
}; };
function runCommand(command) { function runCommand(command: string) {
return new Promise((resolve, reject) => { return new Promise<void>((resolve, reject) => {
exec(command, (error, stdout, stderr) => { exec(command, (error, _stdout, stderr) => {
if (error) { if (error) {
reject(new Error(`exec error: ${stderr}`)); reject(new Error(`exec error: ${stderr}`));
return; return;
@ -223,7 +228,7 @@ async function prepareAndroid() {
await runCommand('adb shell settings put global animator_duration_scale 0.0'); await runCommand('adb shell settings put global animator_duration_scale 0.0');
} }
module.exports = { export {
navigateToWorkspace, navigateToWorkspace,
navigateToLogin, navigateToLogin,
navigateToRegister, navigateToRegister,

View File

@ -1,7 +1,7 @@
const axios = require('axios').default; import axios from 'axios';
const data = require('../data'); import data, { TDataChannels, TDataGroups, TDataTeams, TDataUsers } from '../data';
const random = require('./random'); import random from './random';
const TEAM_TYPE = { const TEAM_TYPE = {
PUBLIC: 0, PUBLIC: 0,
@ -17,7 +17,7 @@ const rocketchat = axios.create({
} }
}); });
const login = async (username, password) => { const login = async (username: string, password: string) => {
console.log(`Logging in as user ${username}`); console.log(`Logging in as user ${username}`);
const response = await rocketchat.post('login', { const response = await rocketchat.post('login', {
user: username, user: username,
@ -30,7 +30,7 @@ const login = async (username, password) => {
return { authToken, userId }; return { authToken, userId };
}; };
const createUser = async (username, password, name, email) => { const createUser = async (username: string, password: string, name: string, email: string) => {
console.log(`Creating user ${username}`); console.log(`Creating user ${username}`);
try { try {
await rocketchat.post('users.create', { await rocketchat.post('users.create', {
@ -45,7 +45,7 @@ const createUser = async (username, password, name, email) => {
} }
}; };
const createChannelIfNotExists = async channelname => { const createChannelIfNotExists = async (channelname: string) => {
console.log(`Creating public channel ${channelname}`); console.log(`Creating public channel ${channelname}`);
try { try {
const room = await rocketchat.post('channels.create', { const room = await rocketchat.post('channels.create', {
@ -65,7 +65,7 @@ const createChannelIfNotExists = async channelname => {
} }
}; };
const createTeamIfNotExists = async teamname => { const createTeamIfNotExists = async (teamname: string) => {
console.log(`Creating private team ${teamname}`); console.log(`Creating private team ${teamname}`);
try { try {
await rocketchat.post('teams.create', { await rocketchat.post('teams.create', {
@ -84,7 +84,7 @@ const createTeamIfNotExists = async teamname => {
} }
}; };
const createGroupIfNotExists = async groupname => { const createGroupIfNotExists = async (groupname: string) => {
console.log(`Creating private group ${groupname}`); console.log(`Creating private group ${groupname}`);
try { try {
await rocketchat.post('groups.create', { await rocketchat.post('groups.create', {
@ -102,7 +102,7 @@ const createGroupIfNotExists = async groupname => {
} }
}; };
const changeChannelJoinCode = async (roomId, joinCode) => { const changeChannelJoinCode = async (roomId: string, joinCode: string) => {
console.log(`Changing channel Join Code ${roomId}`); console.log(`Changing channel Join Code ${roomId}`);
try { try {
await rocketchat.post('method.call/saveRoomSettings', { await rocketchat.post('method.call/saveRoomSettings', {
@ -119,7 +119,7 @@ const changeChannelJoinCode = async (roomId, joinCode) => {
} }
}; };
const sendMessage = async (user, channel, msg, tmid) => { const sendMessage = async (user: { username: string; password: string }, channel: string, msg: string, tmid?: string) => {
console.log(`Sending message to ${channel}`); console.log(`Sending message to ${channel}`);
try { try {
await login(user.username, user.password); await login(user.username, user.password);
@ -136,21 +136,21 @@ const setup = async () => {
for (const userKey in data.users) { for (const userKey in data.users) {
if (Object.prototype.hasOwnProperty.call(data.users, userKey)) { if (Object.prototype.hasOwnProperty.call(data.users, userKey)) {
const user = data.users[userKey]; const user = data.users[userKey as TDataUsers];
await createUser(user.username, user.password, user.username, user.email); await createUser(user.username, user.password, user.username, user.email);
} }
} }
for (const channelKey in data.channels) { for (const channelKey in data.channels) {
if (Object.prototype.hasOwnProperty.call(data.channels, channelKey)) { if (Object.prototype.hasOwnProperty.call(data.channels, channelKey)) {
const channel = data.channels[channelKey]; const channel = data.channels[channelKey as TDataChannels];
const { const {
data: { data: {
channel: { _id } channel: { _id }
} }
} = await createChannelIfNotExists(channel.name); } = await createChannelIfNotExists(channel.name);
if (channel.joinCode) { if ('joinCode' in channel) {
await changeChannelJoinCode(_id, channel.joinCode); await changeChannelJoinCode(_id, channel.joinCode);
} }
} }
@ -160,33 +160,27 @@ const setup = async () => {
for (const groupKey in data.groups) { for (const groupKey in data.groups) {
if (Object.prototype.hasOwnProperty.call(data.groups, groupKey)) { if (Object.prototype.hasOwnProperty.call(data.groups, groupKey)) {
const group = data.groups[groupKey]; const group = data.groups[groupKey as TDataGroups];
await createGroupIfNotExists(group.name); await createGroupIfNotExists(group.name);
} }
} }
for (const teamKey in data.teams) { for (const teamKey in data.teams) {
if (Object.prototype.hasOwnProperty.call(data.teams, teamKey)) { if (Object.prototype.hasOwnProperty.call(data.teams, teamKey)) {
const team = data.teams[teamKey]; const team = data.teams[teamKey as TDataTeams];
await createTeamIfNotExists(team.name); await createTeamIfNotExists(team.name);
} }
} }
}; };
const get = endpoint => { const get = (endpoint: string) => {
console.log(`GET /${endpoint}`); console.log(`GET /${endpoint}`);
return rocketchat.get(endpoint); return rocketchat.get(endpoint);
}; };
const post = (endpoint, body) => { const post = (endpoint: string, body: any) => {
console.log(`POST /${endpoint} ${JSON.stringify(body)}`); console.log(`POST /${endpoint} ${JSON.stringify(body)}`);
return rocketchat.post(endpoint, body); return rocketchat.post(endpoint, body);
}; };
module.exports = { export { setup, sendMessage, get, post, login };
setup,
sendMessage,
get,
post,
login
};

View File

@ -1,4 +1,4 @@
function random(length) { function random(length: number) {
let text = ''; let text = '';
const possible = 'abcdefghijklmnopqrstuvwxyz'; const possible = 'abcdefghijklmnopqrstuvwxyz';
for (let i = 0; i < length; i += 1) { for (let i = 0; i < length; i += 1) {
@ -6,4 +6,5 @@ function random(length) {
} }
return text; return text;
} }
module.exports = random;
export default random;

View File

@ -1,11 +1,22 @@
const { navigateToLogin, login, sleep, tapBack, mockMessage, searchRoom, logout, platformTypes } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data'); import {
navigateToLogin,
login,
sleep,
tapBack,
mockMessage,
searchRoom,
logout,
platformTypes,
TTextMatcher
} from '../../helpers/app';
import data from '../../data';
const testuser = data.users.regular; const testuser = data.users.regular;
const otheruser = data.users.alternate; const otheruser = data.users.alternate;
const checkServer = async server => { const checkServer = async (server: string) => {
const label = `Connected to ${server}`; const label = `Connected to ${server}`;
await element(by.id('rooms-list-view-sidebar')).tap(); await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar-view'))) await waitFor(element(by.id('sidebar-view')))
@ -24,7 +35,7 @@ const checkBanner = async () => {
.withTimeout(10000); .withTimeout(10000);
}; };
async function navigateToRoom(roomName) { async function navigateToRoom(roomName: string) {
await searchRoom(`${roomName}`); await searchRoom(`${roomName}`);
await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); await element(by.id(`rooms-list-view-item-${roomName}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
@ -60,13 +71,12 @@ async function navigateSecurityPrivacy() {
describe('E2E Encryption', () => { describe('E2E Encryption', () => {
const room = `encrypted${data.random}`; const room = `encrypted${data.random}`;
const newPassword = 'abc'; const newPassword = 'abc';
let alertButtonType; let alertButtonType: string;
let scrollViewType; let textMatcher: TTextMatcher;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, scrollViewType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(testuser.username, testuser.password); await login(testuser.username, testuser.password);
}); });
@ -293,14 +303,16 @@ describe('E2E Encryption', () => {
await element(by[textMatcher]('Yes, reset it').and(by.type(alertButtonType))).tap(); await element(by[textMatcher]('Yes, reset it').and(by.type(alertButtonType))).tap();
await sleep(2000); await sleep(2000);
await waitFor(element(by[textMatcher]("You've been logged out by the server. Please log in again."))) // FIXME: The app isn't showing this alert anymore
.toExist() // await waitFor(element(by[textMatcher]("You've been logged out by the server. Please log in again.")))
.withTimeout(20000); // .toExist()
await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); // .withTimeout(20000);
await waitFor(element(by.id('workspace-view'))) // await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
.toBeVisible() // await waitFor(element(by.id('workspace-view')))
.withTimeout(10000); // .toBeVisible()
await element(by.id('workspace-view-login')).tap(); // .withTimeout(10000);
// await element(by.id('workspace-view-login')).tap();
await navigateToLogin();
await waitFor(element(by.id('login-view'))) await waitFor(element(by.id('login-view')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
@ -308,7 +320,7 @@ describe('E2E Encryption', () => {
// TODO: assert 'Save Your Encryption Password' // TODO: assert 'Save Your Encryption Password'
await waitFor(element(by.id('listheader-encryption'))) await waitFor(element(by.id('listheader-encryption')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(5000);
}); });
}); });
}); });

View File

@ -1,14 +1,15 @@
// const OTP = require('otp.js'); // const OTP = require('otp.js');
// const GA = OTP.googleAuthenticator; // const GA = OTP.googleAuthenticator;
import { expect } from 'detox';
const { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes } = require('../../helpers/app'); import { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes, TTextMatcher, sleep } from '../../helpers/app';
const data = require('../../data'); import data from '../../data';
const testuser = data.users.regular; const testuser = data.users.regular;
const otheruser = data.users.alternate; const otheruser = data.users.alternate;
describe('Broadcast room', () => { describe('Broadcast room', () => {
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ textMatcher } = platformTypes[device.getPlatform()]); ({ textMatcher } = platformTypes[device.getPlatform()]);
@ -49,6 +50,7 @@ describe('Broadcast room', () => {
await waitFor(element(by.id(`room-view-title-broadcast${data.random}`))) await waitFor(element(by.id(`room-view-title-broadcast${data.random}`)))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);
await sleep(500);
await element(by.id('room-header')).tap(); await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))) await waitFor(element(by.id('room-actions-view')))
.toBeVisible() .toBeVisible()
@ -123,6 +125,17 @@ describe('Broadcast room', () => {
}); });
it('should reply broadcasted message', async () => { it('should reply broadcasted message', async () => {
await mockMessage('broadcastreply'); // Server is adding 2 spaces in front a reply message
await element(by.id('messagebox-input')).replaceText(`${data.random}broadcastreply`);
await sleep(300);
await element(by.id('messagebox-send-message')).tap();
await waitFor(element(by[textMatcher](`${data.random}message`)))
.toExist()
.withTimeout(10000);
await element(by[textMatcher](`${data.random}message`)).tap();
await sleep(600);
await waitFor(element(by.id(`room-view-title-broadcast${data.random}`)))
.toBeVisible()
.withTimeout(10000);
}); });
}); });

View File

@ -1,5 +1,7 @@
const { navigateToLogin, login, sleep, platformTypes } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data');
import { navigateToLogin, login, sleep, platformTypes, TTextMatcher } from '../../helpers/app';
import data from '../../data';
const profileChangeUser = data.users.profileChanges; const profileChangeUser = data.users.profileChanges;
@ -14,14 +16,12 @@ async function waitForToast() {
} }
describe('Profile screen', () => { describe('Profile screen', () => {
let textInputType; let scrollViewType: string;
let scrollViewType; let textMatcher: TTextMatcher;
let alertButtonType;
let textMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ textInputType, scrollViewType, alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ scrollViewType, textMatcher } = platformTypes[device.getPlatform()]);
await navigateToLogin(); await navigateToLogin();
await login(profileChangeUser.username, profileChangeUser.password); await login(profileChangeUser.username, profileChangeUser.password);
await element(by.id('rooms-list-view-sidebar')).tap(); await element(by.id('rooms-list-view-sidebar')).tap();
@ -107,6 +107,7 @@ describe('Profile screen', () => {
it('should change email and password', async () => { it('should change email and password', async () => {
await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${data.random}@rocket.chat`); await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${data.random}@rocket.chat`);
await element(by.id('profile-view-new-password')).replaceText(`${profileChangeUser.password}new`); await element(by.id('profile-view-new-password')).replaceText(`${profileChangeUser.password}new`);
await sleep(300);
await element(by.id('profile-view-submit')).tap(); await element(by.id('profile-view-submit')).tap();
await waitFor(element(by.id('profile-view-enter-password-sheet'))) await waitFor(element(by.id('profile-view-enter-password-sheet')))
.toBeVisible() .toBeVisible()

View File

@ -1,12 +1,13 @@
const { navigateToLogin, login, platformTypes } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data'); import { navigateToLogin, login, platformTypes, TTextMatcher } from '../../helpers/app';
import data from '../../data';
const testuser = data.users.regular; const testuser = data.users.regular;
describe('Settings screen', () => { describe('Settings screen', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,5 +1,7 @@
const data = require('../../data'); import { expect } from 'detox';
const { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes } = require('../../helpers/app');
import data from '../../data';
import { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes, TTextMatcher } from '../../helpers/app';
const testuser = data.users.regular; const testuser = data.users.regular;
const room = data.channels.detoxpublic.name; const room = data.channels.detoxpublic.name;
@ -20,8 +22,8 @@ async function navigateToRoomActions() {
} }
describe('Join public room', () => { describe('Join public room', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,5 +1,7 @@
const { navigateToLogin, login, sleep } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data');
import { navigateToLogin, login, sleep } from '../../helpers/app';
import data from '../../data';
const testuser = data.users.regular; const testuser = data.users.regular;

View File

@ -1,7 +1,7 @@
const data = require('../../data'); import data from '../../data';
const { navigateToLogin, login, checkServer, platformTypes, sleep } = require('../../helpers/app'); import { navigateToLogin, login, checkServer, sleep } from '../../helpers/app';
const reopenAndCheckServer = async server => { const reopenAndCheckServer = async (server: string) => {
await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true }); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true });
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()

View File

@ -1,5 +1,7 @@
const data = require('../../data'); import { expect } from 'detox';
const { navigateToLogin, login, mockMessage, searchRoom, sleep } = require('../../helpers/app');
import data from '../../data';
import { navigateToLogin, login, mockMessage, searchRoom } from '../../helpers/app';
const testuser = data.users.regular; const testuser = data.users.regular;
const room = data.channels.detoxpublicprotected.name; const room = data.channels.detoxpublicprotected.name;

View File

@ -1,9 +1,9 @@
const data = require('../../data'); import data from '../../data';
const { navigateToLogin, login, tapBack, sleep } = require('../../helpers/app'); import { navigateToLogin, login, tapBack, sleep } from '../../helpers/app';
const testuser = data.users.regular; const testuser = data.users.regular;
async function navigateToRoom(search) { async function navigateToRoom(search: string) {
await element(by.id('directory-view-search')).replaceText(search); await element(by.id('directory-view-search')).replaceText(search);
await waitFor(element(by.id(`directory-view-item-${search}`))) await waitFor(element(by.id(`directory-view-item-${search}`)))
.toBeVisible() .toBeVisible()

View File

@ -1,9 +1,9 @@
const data = require('../../data'); import data from '../../data';
const { sleep, navigateToLogin, login, checkServer, platformTypes } = require('../../helpers/app'); import { sleep, navigateToLogin, login, checkServer, platformTypes, TTextMatcher } from '../../helpers/app';
describe('Delete server', () => { describe('Delete server', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,31 +1,32 @@
const data = require('../../data'); import EJSON from 'ejson';
const { tapBack, checkServer, navigateToRegister, platformTypes } = require('../../helpers/app');
const { get, login, sendMessage } = require('../../helpers/data_setup'); import data from '../../data';
import { tapBack, checkServer, navigateToRegister, platformTypes, TTextMatcher } from '../../helpers/app';
import { get, login, sendMessage } from '../../helpers/data_setup';
const DEEPLINK_METHODS = { AUTH: 'auth', ROOM: 'room' }; const DEEPLINK_METHODS = { AUTH: 'auth', ROOM: 'room' };
let amp = '&'; let amp = '&';
const getDeepLink = (method, server, params) => { const getDeepLink = (method: string, server: string, params?: string) => {
const deeplink = `rocketchat://${method}?host=${server.replace(/^(http:\/\/|https:\/\/)/, '')}${amp}${params}`; const deeplink = `rocketchat://${method}?host=${server.replace(/^(http:\/\/|https:\/\/)/, '')}${amp}${params}`;
console.log(`Deeplinking to: ${deeplink}`); console.log(`Deeplinking to: ${deeplink}`);
return deeplink; return deeplink;
}; };
describe('Deep linking', () => { describe('Deep linking', () => {
let userId; let userId: string;
let authToken; let authToken: string;
let scrollViewType; let scrollViewType: string;
let threadId; let threadId: string;
let textMatcher; let textMatcher: TTextMatcher;
let alertButtonType;
const threadMessage = `to-thread-${data.random}`; const threadMessage = `to-thread-${data.random}`;
before(async () => { before(async () => {
const loginResult = await login(data.users.regular.username, data.users.regular.password); const loginResult = await login(data.users.regular.username, data.users.regular.password);
({ userId, authToken } = loginResult); ({ userId, authToken } = loginResult);
const deviceType = device.getPlatform(); const deviceType = device.getPlatform();
amp = deviceType === 'android' ? '\\&' : '&'; amp = deviceType === 'android' ? '\\&' : '&';
({ scrollViewType, textMatcher, alertButtonType } = platformTypes[deviceType]); ({ scrollViewType, textMatcher } = platformTypes[deviceType]);
// create a thread with api // create a thread with api
const result = await sendMessage(data.users.regular, data.groups.alternate2.name, threadMessage); const result = await sendMessage(data.users.regular, data.groups.alternate2.name, threadMessage);
threadId = result.message._id; threadId = result.message._id;
@ -142,6 +143,41 @@ describe('Deep linking', () => {
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
}); });
it('should simulate a tap on a push notification and navigate to the room', async () => {
/**
* Ideally, we would repeat this test to simulate a resume from background,
* but for some reason it was not working as expected
* This was always turning to false right before running the logic https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/18f359a8ef9691144970c0c1fad990f82096b024/app/lib/notifications/push.ts#L58
*/
// await device.sendToHome();
await device.launchApp({
newInstance: true,
userNotification: {
trigger: {
type: 'push'
},
title: 'From push',
body: 'Body',
badge: 1,
payload: {
ejson: EJSON.stringify({
rid: null,
host: data.server,
name: data.groups.private.name,
type: 'p'
})
}
}
});
await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`)))
.toExist()
.withTimeout(30000);
await tapBack();
await waitFor(element(by.id('rooms-list-view')))
.toBeVisible()
.withTimeout(2000);
});
}); });
describe('Others', () => { describe('Others', () => {

View File

@ -1,9 +1,11 @@
const { navigateToLogin, login, sleep } = require('../../helpers/app'); import { expect } from 'detox';
const { post } = require('../../helpers/data_setup');
const data = require('../../data'); import { navigateToLogin, login, sleep } from '../../helpers/app';
import { post } from '../../helpers/data_setup';
import data from '../../data';
const testuser = data.users.regular; const testuser = data.users.regular;
const defaultLaunchArgs = { permissions: { notifications: 'YES' } }; const defaultLaunchArgs = { permissions: { notifications: 'YES' } } as Detox.DeviceLaunchAppConfig;
const navToLanguage = async () => { const navToLanguage = async () => {
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))

View File

@ -1,5 +1,7 @@
const { login, navigateToLogin, sleep } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data');
import { login, navigateToLogin } from '../../helpers/app';
import data from '../../data';
const goToDisplayPref = async () => { const goToDisplayPref = async () => {
await expect(element(by.id('rooms-list-view-sidebar'))).toBeVisible(); await expect(element(by.id('rooms-list-view-sidebar'))).toBeVisible();

View File

@ -1,11 +1,12 @@
const detox = require('detox'); import detox from 'detox';
const adapter = require('detox/runners/mocha/adapter'); import adapter from 'detox/runners/mocha/adapter';
const config = require('../../package.json').detox; import { detox as config } from '../../package.json';
const { setup } = require('../helpers/data_setup'); import { setup } from '../helpers/data_setup';
const { prepareAndroid } = require('../helpers/app'); import { prepareAndroid } from '../helpers/app';
before(async () => { before(async () => {
// @ts-ignore
await Promise.all([setup(), detox.init(config, { launchApp: false })]); await Promise.all([setup(), detox.init(config, { launchApp: false })]);
await prepareAndroid(); // Make Android less flaky await prepareAndroid(); // Make Android less flaky
// await dataSetup() // await dataSetup()
@ -14,10 +15,12 @@ before(async () => {
}); });
beforeEach(async function () { beforeEach(async function () {
// @ts-ignore
await adapter.beforeEach(this); await adapter.beforeEach(this);
}); });
afterEach(async function () { afterEach(async function () {
// @ts-ignore
await adapter.afterEach(this); await adapter.afterEach(this);
}); });

View File

@ -1,9 +1,11 @@
const data = require('../../data'); import { expect } from 'detox';
const { platformTypes } = require('../../helpers/app');
import { TTextMatcher, platformTypes } from '../../helpers/app';
import data from '../../data';
describe('Onboarding', () => { describe('Onboarding', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,4 +1,6 @@
const { navigateToRegister, navigateToLogin } = require('../../helpers/app'); import { expect } from 'detox';
import { navigateToRegister, navigateToLogin } from '../../helpers/app';
describe('Legal screen', () => { describe('Legal screen', () => {
describe('From Login', () => { describe('From Login', () => {

View File

@ -1,9 +1,11 @@
const data = require('../../data'); import { expect } from 'detox';
const { navigateToLogin, platformTypes } = require('../../helpers/app');
import data from '../../data';
import { navigateToLogin, platformTypes, TTextMatcher } from '../../helpers/app';
describe('Forgot password screen', () => { describe('Forgot password screen', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,9 +1,11 @@
const { navigateToRegister, platformTypes } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data');
import { navigateToRegister, platformTypes, TTextMatcher } from '../../helpers/app';
import data from '../../data';
describe('Create user screen', () => { describe('Create user screen', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
@ -50,17 +52,18 @@ describe('Create user screen', () => {
// await element(by.id('register-view-submit')).tap(); // await element(by.id('register-view-submit')).tap();
// }); // });
it('should submit email already taken and raise error', async () => { // TODO: When server handle two errors in sequence, the server return Too many requests and force to wait for some time.
await element(by.id('register-view-name')).replaceText(data.registeringUser.username); // it('should submit email already taken and raise error', async () => {
await element(by.id('register-view-username')).replaceText(data.registeringUser.username); // await element(by.id('register-view-name')).replaceText(data.registeringUser.username);
await element(by.id('register-view-email')).replaceText(data.users.existing.email); // await element(by.id('register-view-username')).replaceText(data.registeringUser.username);
await element(by.id('register-view-password')).replaceText(data.registeringUser.password); // await element(by.id('register-view-email')).replaceText(data.users.existing.email);
await element(by.id('register-view-submit')).tap(); // await element(by.id('register-view-password')).replaceText(data.registeringUser.password);
await waitFor(element(by[textMatcher]('Email already exists. [403]')).atIndex(0)) // await element(by.id('register-view-submit')).tap();
.toExist() // await waitFor(element(by[textMatcher]('Email already exists. [403]')).atIndex(0))
.withTimeout(10000); // .toExist()
await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap(); // .withTimeout(10000);
}); // await element(by[textMatcher]('OK').and(by.type(alertButtonType))).tap();
// });
it('should submit username already taken and raise error', async () => { it('should submit username already taken and raise error', async () => {
await element(by.id('register-view-name')).replaceText(data.registeringUser.username); await element(by.id('register-view-name')).replaceText(data.registeringUser.username);

View File

@ -1,9 +1,11 @@
const { navigateToLogin, tapBack, platformTypes, navigateToWorkspace, login } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data');
import { navigateToLogin, tapBack, platformTypes, navigateToWorkspace, login, TTextMatcher } from '../../helpers/app';
import data from '../../data';
describe('Login screen', () => { describe('Login screen', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,5 +1,7 @@
const { login, navigateToLogin, logout, tapBack, searchRoom } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data');
import { login, navigateToLogin, logout, tapBack, searchRoom } from '../../helpers/app';
import data from '../../data';
describe('Rooms list screen', () => { describe('Rooms list screen', () => {
before(async () => { before(async () => {

View File

@ -1,5 +1,7 @@
const { login, navigateToLogin, logout, tapBack } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data');
import { login, navigateToLogin, logout, tapBack } from '../../helpers/app';
import data from '../../data';
describe('Server history', () => { describe('Server history', () => {
before(async () => { before(async () => {

View File

@ -1,9 +1,11 @@
const data = require('../../data'); import { expect } from 'detox';
const { tapBack, navigateToLogin, login, tryTapping, platformTypes } = require('../../helpers/app');
import data from '../../data';
import { tapBack, navigateToLogin, login, tryTapping, platformTypes, TTextMatcher } from '../../helpers/app';
describe('Create room screen', () => { describe('Create room screen', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,5 +1,7 @@
const data = require('../../data'); import { expect } from 'detox';
const {
import data from '../../data';
import {
navigateToLogin, navigateToLogin,
login, login,
mockMessage, mockMessage,
@ -10,10 +12,11 @@ const {
pinMessage, pinMessage,
dismissReviewNag, dismissReviewNag,
tryTapping, tryTapping,
platformTypes platformTypes,
} = require('../../helpers/app'); TTextMatcher
} from '../../helpers/app';
async function navigateToRoom(roomName) { async function navigateToRoom(roomName: string) {
await searchRoom(`${roomName}`); await searchRoom(`${roomName}`);
await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); await element(by.id(`rooms-list-view-item-${roomName}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
@ -23,8 +26,8 @@ async function navigateToRoom(roomName) {
describe('Room screen', () => { describe('Room screen', () => {
const mainRoom = data.groups.private.name; const mainRoom = data.groups.private.name;
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });

View File

@ -1,5 +1,7 @@
const data = require('../../data'); import { expect } from 'detox';
const {
import data from '../../data';
import {
navigateToLogin, navigateToLogin,
login, login,
tapBack, tapBack,
@ -8,11 +10,13 @@ const {
mockMessage, mockMessage,
starMessage, starMessage,
pinMessage, pinMessage,
platformTypes platformTypes,
} = require('../../helpers/app'); TTextMatcher
} from '../../helpers/app';
const { sendMessage } = require('../../helpers/data_setup'); const { sendMessage } = require('../../helpers/data_setup');
async function navigateToRoomActions(type) { async function navigateToRoomActions(type: string) {
let room; let room;
if (type === 'd') { if (type === 'd') {
room = 'rocket.cat'; room = 'rocket.cat';
@ -53,8 +57,8 @@ async function waitForToast() {
} }
describe('Room actions screen', () => { describe('Room actions screen', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await navigateToLogin(); await navigateToLogin();
@ -398,7 +402,7 @@ describe('Room actions screen', () => {
.withTimeout(2000); .withTimeout(2000);
}); });
const openActionSheet = async username => { const openActionSheet = async (username: string) => {
await waitFor(element(by.id(`room-members-view-item-${username}`))) await waitFor(element(by.id(`room-members-view-item-${username}`)))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
@ -461,7 +465,7 @@ describe('Room actions screen', () => {
.toBeNotVisible() .toBeNotVisible()
.withTimeout(60000); .withTimeout(60000);
await element(by.id('room-members-view-search')).tap(); await element(by.id('room-members-view-search')).tap();
await element(by.id('room-members-view-search')).clearText(''); await element(by.id('room-members-view-search')).clearText();
await waitFor(element(by.id(`room-members-view-item-${user.username}`))) await waitFor(element(by.id(`room-members-view-item-${user.username}`)))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);
@ -484,7 +488,7 @@ describe('Room actions screen', () => {
it('should clear search', async () => { it('should clear search', async () => {
await element(by.id('room-members-view-search')).tap(); await element(by.id('room-members-view-search')).tap();
await element(by.id('room-members-view-search')).clearText(''); await element(by.id('room-members-view-search')).clearText();
await waitFor(element(by.id(`room-members-view-item-${user.username}`))) await waitFor(element(by.id(`room-members-view-item-${user.username}`)))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);

View File

@ -1,5 +1,7 @@
const { navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes } = require('../../helpers/app'); import { expect } from 'detox';
const data = require('../../data');
import { TTextMatcher, navigateToLogin, login, mockMessage, tapBack, searchRoom, platformTypes } from '../../helpers/app';
import data from '../../data';
const channel = data.groups.private.name; const channel = data.groups.private.name;
@ -12,7 +14,7 @@ const navigateToRoom = async () => {
}; };
describe('Discussion', () => { describe('Discussion', () => {
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true });
({ textMatcher } = platformTypes[device.getPlatform()]); ({ textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,5 +1,7 @@
const data = require('../../data'); import { expect } from 'detox';
const {
import data from '../../data';
import {
navigateToLogin, navigateToLogin,
login, login,
mockMessage, mockMessage,
@ -7,10 +9,11 @@ const {
sleep, sleep,
searchRoom, searchRoom,
platformTypes, platformTypes,
dismissReviewNag dismissReviewNag,
} = require('../../helpers/app'); TTextMatcher
} from '../../helpers/app';
async function navigateToRoom(roomName) { async function navigateToRoom(roomName: string) {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
@ -23,7 +26,7 @@ async function navigateToRoom(roomName) {
describe('Threads', () => { describe('Threads', () => {
const mainRoom = data.groups.private.name; const mainRoom = data.groups.private.name;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
({ textMatcher } = platformTypes[device.getPlatform()]); ({ textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,8 +1,8 @@
const data = require('../../data'); import data from '../../data';
const { navigateToLogin, login, platformTypes } = require('../../helpers/app'); import { navigateToLogin, login, platformTypes, TTextMatcher } from '../../helpers/app';
describe('Group DM', () => { describe('Group DM', () => {
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ textMatcher } = platformTypes[device.getPlatform()]); ({ textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,8 +1,10 @@
const data = require('../../data'); import { expect } from 'detox';
const { navigateToLogin, login, searchRoom, sleep, platformTypes } = require('../../helpers/app');
const { sendMessage } = require('../../helpers/data_setup');
async function navigateToRoom(user) { import data from '../../data';
import { navigateToLogin, login, searchRoom, sleep, platformTypes, TTextMatcher } from '../../helpers/app';
import { sendMessage } from '../../helpers/data_setup';
async function navigateToRoom(user: string) {
await searchRoom(`${user}`); await searchRoom(`${user}`);
await element(by.id(`rooms-list-view-item-${user}`)).tap(); await element(by.id(`rooms-list-view-item-${user}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
@ -12,7 +14,7 @@ async function navigateToRoom(user) {
describe('Mark as unread', () => { describe('Mark as unread', () => {
const user = data.users.alternate.username; const user = data.users.alternate.username;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });

View File

@ -1,9 +1,11 @@
const data = require('../../data'); import { expect } from 'detox';
const { navigateToLogin, login, tapBack, sleep, searchRoom, platformTypes } = require('../../helpers/app');
import data from '../../data';
import { navigateToLogin, login, tapBack, sleep, searchRoom, platformTypes, TTextMatcher } from '../../helpers/app';
const privateRoomName = data.groups.private.name; const privateRoomName = data.groups.private.name;
async function navigateToRoomInfo(type) { async function navigateToRoomInfo(type: string) {
let room; let room;
if (type === 'd') { if (type === 'd') {
room = 'rocket.cat'; room = 'rocket.cat';
@ -25,7 +27,7 @@ async function navigateToRoomInfo(type) {
.withTimeout(2000); .withTimeout(2000);
} }
async function swipe(direction) { async function swipe(direction: Detox.Direction) {
await element(by.id('room-info-edit-view-list')).swipe(direction, 'fast', 0.8); await element(by.id('room-info-edit-view-list')).swipe(direction, 'fast', 0.8);
} }
@ -34,8 +36,8 @@ async function waitForToast() {
} }
describe('Room info screen', () => { describe('Room info screen', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);
@ -174,6 +176,7 @@ describe('Room info screen', () => {
await waitFor(element(by.id('room-info-edit-view'))) await waitFor(element(by.id('room-info-edit-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await sleep(2000);
await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}`); await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}`);
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
@ -182,6 +185,7 @@ describe('Room info screen', () => {
}); });
it('should reset form', async () => { it('should reset form', async () => {
await sleep(2000);
await element(by.id('room-info-edit-view-name')).replaceText('abc'); await element(by.id('room-info-edit-view-name')).replaceText('abc');
await element(by.id('room-info-edit-view-description')).replaceText('abc'); await element(by.id('room-info-edit-view-description')).replaceText('abc');
await element(by.id('room-info-edit-view-topic')).replaceText('abc'); await element(by.id('room-info-edit-view-topic')).replaceText('abc');
@ -194,6 +198,7 @@ describe('Room info screen', () => {
await swipe('up'); await swipe('up');
await element(by.id('room-info-edit-view-reset')).tap(); await element(by.id('room-info-edit-view-reset')).tap();
// after reset // after reset
await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.5);
await expect(element(by.id('room-info-edit-view-name'))).toHaveText(privateRoomName); await expect(element(by.id('room-info-edit-view-name'))).toHaveText(privateRoomName);
await expect(element(by.id('room-info-edit-view-description'))).toHaveText(''); await expect(element(by.id('room-info-edit-view-description'))).toHaveText('');
await expect(element(by.id('room-info-edit-view-topic'))).toHaveText(''); await expect(element(by.id('room-info-edit-view-topic'))).toHaveText('');
@ -226,6 +231,7 @@ describe('Room info screen', () => {
await waitFor(element(by.id('room-info-edit-view'))) await waitFor(element(by.id('room-info-edit-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await sleep(2000);
await element(by.id('room-info-edit-view-topic')).replaceText('new topic'); await element(by.id('room-info-edit-view-topic')).replaceText('new topic');
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
@ -245,6 +251,7 @@ describe('Room info screen', () => {
await waitFor(element(by.id('room-info-edit-view'))) await waitFor(element(by.id('room-info-edit-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await sleep(2000);
await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement'); await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement');
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
@ -264,6 +271,7 @@ describe('Room info screen', () => {
await waitFor(element(by.id('room-info-edit-view'))) await waitFor(element(by.id('room-info-edit-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await sleep(2000);
await element(by.id('room-info-edit-view-password')).replaceText('password'); await element(by.id('room-info-edit-view-password')).replaceText('password');
await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();

View File

@ -1,7 +1,9 @@
const data = require('../../data'); import { expect } from 'detox';
const { navigateToLogin, tapBack, login, searchRoom, sleep, platformTypes } = require('../../helpers/app');
async function navigateToRoom(roomName) { import data from '../../data';
import { navigateToLogin, tapBack, login, searchRoom, sleep, platformTypes, TTextMatcher } from '../../helpers/app';
async function navigateToRoom(roomName: string) {
await searchRoom(`${roomName}`); await searchRoom(`${roomName}`);
await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); await element(by.id(`rooms-list-view-item-${roomName}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
@ -12,8 +14,8 @@ async function navigateToRoom(roomName) {
.withTimeout(5000); .withTimeout(5000);
} }
let textMatcher; let textMatcher: TTextMatcher;
let alertButtonType; let alertButtonType: string;
async function clearCache() { async function clearCache() {
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
@ -123,6 +125,7 @@ describe('Room', () => {
await waitFor(element(by[textMatcher]('30')).atIndex(1)) await waitFor(element(by[textMatcher]('30')).atIndex(1))
.toExist() .toExist()
.withTimeout(30000); .withTimeout(30000);
await sleep(1000);
await element(by[textMatcher]('30')).atIndex(1).tap(); await element(by[textMatcher]('30')).atIndex(1).tap();
await waitForLoading(); await waitForLoading();
await waitFor(element(by[textMatcher]('30')).atIndex(0)) await waitFor(element(by[textMatcher]('30')).atIndex(0))
@ -194,7 +197,7 @@ describe('Room', () => {
}); });
}); });
const expectThreadMessages = async message => { const expectThreadMessages = async (message: string) => {
await waitFor(element(by.id('room-view-title-thread 1'))) await waitFor(element(by.id('room-view-title-thread 1')))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);

View File

@ -1,11 +1,13 @@
const data = require('../../data'); import { expect } from 'detox';
const { navigateToLogin, login, platformTypes } = require('../../helpers/app');
import data from '../../data';
import { navigateToLogin, login, platformTypes, TTextMatcher } from '../../helpers/app';
const teamName = `team-${data.random}`; const teamName = `team-${data.random}`;
describe('Create team screen', () => { describe('Create team screen', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);

View File

@ -1,7 +1,9 @@
const data = require('../../data'); import { expect } from 'detox';
const { navigateToLogin, login, tapBack, sleep, searchRoom, platformTypes } = require('../../helpers/app');
async function navigateToRoom(roomName) { import data from '../../data';
import { navigateToLogin, login, tapBack, sleep, searchRoom, platformTypes, TTextMatcher } from '../../helpers/app';
async function navigateToRoom(roomName: string) {
await searchRoom(`${roomName}`); await searchRoom(`${roomName}`);
await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); await element(by.id(`rooms-list-view-item-${roomName}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
@ -9,7 +11,7 @@ async function navigateToRoom(roomName) {
.withTimeout(5000); .withTimeout(5000);
} }
async function openActionSheet(username) { async function openActionSheet(username: string) {
await waitFor(element(by.id(`room-members-view-item-${username}`))) await waitFor(element(by.id(`room-members-view-item-${username}`)))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
@ -48,7 +50,13 @@ async function waitForToast() {
await sleep(1000); await sleep(1000);
} }
async function swipeTillVisible(container, find, direction = 'up', delta = 0.3, speed = 'slow') { async function swipeTillVisible(
container: Detox.NativeMatcher,
find: Detox.NativeMatcher,
direction: Detox.Direction = 'up',
delta = 0.3,
speed: Detox.Speed = 'slow'
) {
let found = false; let found = false;
while (!found) { while (!found) {
try { try {
@ -67,8 +75,8 @@ describe('Team', () => {
const user = data.users.alternate; const user = data.users.alternate;
const room = `private${data.random}-channel-team`; const room = `private${data.random}-channel-team`;
const existingRoom = data.groups.alternate.name; const existingRoom = data.groups.alternate.name;
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
@ -388,7 +396,7 @@ describe('Team', () => {
.toBeNotVisible() .toBeNotVisible()
.withTimeout(60000); .withTimeout(60000);
await element(by.id('room-members-view-search')).tap(); await element(by.id('room-members-view-search')).tap();
await element(by.id('room-members-view-search')).clearText(''); await element(by.id('room-members-view-search')).clearText();
await waitFor(element(by.id(`room-members-view-item-${user.username}`))) await waitFor(element(by.id(`room-members-view-item-${user.username}`)))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);

View File

@ -1,10 +1,10 @@
const data = require('../../data'); import data from '../../data';
const { navigateToLogin, login, tapBack, searchRoom, sleep, platformTypes } = require('../../helpers/app'); import { navigateToLogin, login, tapBack, searchRoom, platformTypes, TTextMatcher } from '../../helpers/app';
const toBeConverted = `to-be-converted-${data.random}`; const toBeConverted = `to-be-converted-${data.random}`;
const toBeMoved = `to-be-moved-${data.random}`; const toBeMoved = `to-be-moved-${data.random}`;
const createChannel = async room => { const createChannel = async (room: string) => {
await waitFor(element(by.id('rooms-list-view-create-channel'))) await waitFor(element(by.id('rooms-list-view-create-channel')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
@ -40,7 +40,7 @@ const createChannel = async room => {
.withTimeout(60000); .withTimeout(60000);
}; };
async function navigateToRoom(room) { async function navigateToRoom(room: string) {
await searchRoom(`${room}`); await searchRoom(`${room}`);
await element(by.id(`rooms-list-view-item-${room}`)).tap(); await element(by.id(`rooms-list-view-item-${room}`)).tap();
await waitFor(element(by.id('room-view'))) await waitFor(element(by.id('room-view')))
@ -48,7 +48,7 @@ async function navigateToRoom(room) {
.withTimeout(5000); .withTimeout(5000);
} }
async function navigateToRoomActions(room) { async function navigateToRoomActions(room: string) {
await navigateToRoom(room); await navigateToRoom(room);
await element(by.id('room-header')).tap(); await element(by.id('room-header')).tap();
await waitFor(element(by.id('room-actions-view'))) await waitFor(element(by.id('room-actions-view')))
@ -57,8 +57,8 @@ async function navigateToRoomActions(room) {
} }
describe('Move/Convert Team', () => { describe('Move/Convert Team', () => {
let alertButtonType; let alertButtonType: string;
let textMatcher; let textMatcher: TTextMatcher;
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]); ({ alertButtonType, textMatcher } = platformTypes[device.getPlatform()]);

23
e2e/tsconfig.json Normal file
View File

@ -0,0 +1,23 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"jsx": "react",
"target": "es2018",
"module": "commonjs",
"importHelpers": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"types": ["node", "detox", "mocha"]
},
"include": ["./**/*.ts"],
"exclude": ["../node_modules"]
}

View File

@ -13,13 +13,13 @@ PODS:
- ExpoModulesCore - ExpoModulesCore
- EXLocalAuthentication (12.2.0): - EXLocalAuthentication (12.2.0):
- ExpoModulesCore - ExpoModulesCore
- Expo (45.0.5): - Expo (46.0.9):
- ExpoModulesCore - ExpoModulesCore
- ExpoHaptics (11.2.0): - ExpoHaptics (11.2.0):
- ExpoModulesCore - ExpoModulesCore
- ExpoKeepAwake (10.1.1): - ExpoKeepAwake (10.1.1):
- ExpoModulesCore - ExpoModulesCore
- ExpoModulesCore (0.9.2): - ExpoModulesCore (0.11.4):
- React-Core - React-Core
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- ExpoWebBrowser (10.2.1): - ExpoWebBrowser (10.2.1):
@ -592,7 +592,7 @@ DEPENDENCIES:
- EXAV (from `../node_modules/expo-av/ios`) - EXAV (from `../node_modules/expo-av/ios`)
- EXFileSystem (from `../node_modules/expo-file-system/ios`) - EXFileSystem (from `../node_modules/expo-file-system/ios`)
- EXLocalAuthentication (from `../node_modules/expo-local-authentication/ios`) - EXLocalAuthentication (from `../node_modules/expo-local-authentication/ios`)
- Expo (from `../node_modules/expo/ios`) - Expo (from `../node_modules/expo`)
- ExpoHaptics (from `../node_modules/expo-haptics/ios`) - ExpoHaptics (from `../node_modules/expo-haptics/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core/ios`) - ExpoModulesCore (from `../node_modules/expo-modules-core/ios`)
@ -719,7 +719,7 @@ EXTERNAL SOURCES:
EXLocalAuthentication: EXLocalAuthentication:
:path: "../node_modules/expo-local-authentication/ios" :path: "../node_modules/expo-local-authentication/ios"
Expo: Expo:
:path: "../node_modules/expo/ios" :path: "../node_modules/expo"
ExpoHaptics: ExpoHaptics:
:path: "../node_modules/expo-haptics/ios" :path: "../node_modules/expo-haptics/ios"
ExpoKeepAwake: ExpoKeepAwake:
@ -894,10 +894,10 @@ SPEC CHECKSUMS:
EXAV: 88f61c5af8415715b7ee51f084c1020235b85c56 EXAV: 88f61c5af8415715b7ee51f084c1020235b85c56
EXFileSystem: 2aa2d9289f84bca9532b9ccbd81504fa31eb1ded EXFileSystem: 2aa2d9289f84bca9532b9ccbd81504fa31eb1ded
EXLocalAuthentication: 7f37b242eae73f9acf111d39bdee3f1379e68902 EXLocalAuthentication: 7f37b242eae73f9acf111d39bdee3f1379e68902
Expo: b9fff0a1eac0f424fc68ea49b4347fb308e52e17 Expo: 73412414e62f5cbc6e713def821de70b92cd3ad6
ExpoHaptics: ad58ec96a25e57579c14a47c7d71f0de0de8656a ExpoHaptics: ad58ec96a25e57579c14a47c7d71f0de0de8656a
ExpoKeepAwake: c0c494b442ecd8122974c13b93ccfb57bd408e88 ExpoKeepAwake: c0c494b442ecd8122974c13b93ccfb57bd408e88
ExpoModulesCore: e4278a668e8c13c0269ed8b8a4200989deea2973 ExpoModulesCore: e281bb7b78ea47e227dd5af94d04b24d8b2e1255
ExpoWebBrowser: 4b5f9633e5f169dc948587cb6d26d2d1d1406187 ExpoWebBrowser: 4b5f9633e5f169dc948587cb6d26d2d1d1406187
EXVideoThumbnails: 19e055dc3245b53c536da9e0ef9c618fd2118297 EXVideoThumbnails: 19e055dc3245b53c536da9e0ef9c618fd2118297
FBLazyVector: a7a655862f6b09625d11c772296b01cd5164b648 FBLazyVector: a7a655862f6b09625d11c772296b01cd5164b648

View File

@ -1767,7 +1767,7 @@
INFOPLIST_FILE = NotificationService/Info.plist; INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 4.30.0; MARKETING_VERSION = 4.31.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
@ -1806,7 +1806,7 @@
INFOPLIST_FILE = NotificationService/Info.plist; INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 4.30.0; MARKETING_VERSION = 4.31.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;

View File

@ -10,6 +10,9 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <React/RCTBridgeDelegate.h> #import <React/RCTBridgeDelegate.h>
#import <Expo/Expo.h> #import <Expo/Expo.h>
// https://github.com/expo/expo/issues/17705#issuecomment-1196251146
#import "ExpoModulesCore-Swift.h"
#import "RocketChatRN-Swift.h"
@interface AppDelegate : EXAppDelegateWrapper <UIApplicationDelegate, RCTBridgeDelegate> @interface AppDelegate : EXAppDelegateWrapper <UIApplicationDelegate, RCTBridgeDelegate>

View File

@ -46,7 +46,6 @@
} }
[Bugsnag start]; [Bugsnag start];
// UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"RocketChatRN", nil);
UIView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"RocketChatRN" initialProperties:nil]; UIView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"RocketChatRN" initialProperties:nil];
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
@ -60,7 +59,9 @@
rootViewController.view = rootView; rootViewController.view = rootView;
self.window.rootViewController = rootViewController; self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible]; [self.window makeKeyAndVisible];
[super application:application didFinishLaunchingWithOptions:launchOptions]; [RNNotifications startMonitorNotifications];
[ReplyNotification configure];
// AppGroup MMKV // AppGroup MMKV
NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]].path; NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]].path;
[MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogNone]; [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogNone];

View File

@ -26,7 +26,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>4.30.0</string> <string>4.31.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@ -68,30 +68,16 @@
</dict> </dict>
</dict> </dict>
</dict> </dict>
<key>NSCalendarsUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your calendar</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Take photos to share with other users</string> <string>Take photos to share with other users</string>
<key>NSContactsUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your contacts</string>
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>
<string>Unlock the app with FaceID</string> <string>Unlock the app with FaceID</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This permission stems from a library we use and will never be called anyway. If you see this, deny access</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This permission stems from a library we use and will never be called anyway. If you see this, deny access</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This permission stems from a library we use and will never be called anyway. If you see this, deny access</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>Use your microphone to record audio messages</string> <string>Use your microphone to record audio messages</string>
<key>NSMotionUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your device&apos;s accelerometer</string>
<key>NSPhotoLibraryAddUsageDescription</key> <key>NSPhotoLibraryAddUsageDescription</key>
<string>Give $(PRODUCT_NAME) permission to save photos</string> <string>Give $(PRODUCT_NAME) permission to save photos</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>Upload photos to share with other users or to change your avatar</string> <string>Upload photos to share with other users or to change your avatar</string>
<key>NSRemindersUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your reminders</string>
<key>UIAppFonts</key> <key>UIAppFonts</key>
<array> <array>
<string>custom.ttf</string> <string>custom.ttf</string>

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "rocket-chat-reactnative", "name": "rocket-chat-reactnative",
"version": "4.30.0", "version": "4.31.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "react-native start", "start": "react-native start",
@ -61,7 +61,7 @@
"commonmark-react-renderer": "git+https://github.com/RocketChat/commonmark-react-renderer.git", "commonmark-react-renderer": "git+https://github.com/RocketChat/commonmark-react-renderer.git",
"dequal": "^2.0.2", "dequal": "^2.0.2",
"ejson": "2.2.1", "ejson": "2.2.1",
"expo": "^45.0.5", "expo": "^46.0.9",
"expo-apple-authentication": "4.2.1", "expo-apple-authentication": "4.2.1",
"expo-av": "11.2.3", "expo-av": "11.2.3",
"expo-file-system": "14.0.0", "expo-file-system": "14.0.0",
@ -104,7 +104,7 @@
"react-native-mmkv-storage": "^0.7.6", "react-native-mmkv-storage": "^0.7.6",
"react-native-modal": "13.0.1", "react-native-modal": "13.0.1",
"react-native-navigation-bar-color": "2.0.1", "react-native-navigation-bar-color": "2.0.1",
"react-native-notifications": "^4.2.4", "react-native-notifications": "4.2.4",
"react-native-notifier": "1.6.1", "react-native-notifier": "1.6.1",
"react-native-orientation-locker": "1.1.8", "react-native-orientation-locker": "1.1.8",
"react-native-picker-select": "^8.0.4", "react-native-picker-select": "^8.0.4",
@ -137,6 +137,7 @@
"ua-parser-js": "^1.0.2", "ua-parser-js": "^1.0.2",
"uri-js": "^4.4.1", "uri-js": "^4.4.1",
"url-parse": "1.5.10", "url-parse": "1.5.10",
"use-debounce": "^8.0.4",
"use-deep-compare-effect": "1.6.1", "use-deep-compare-effect": "1.6.1",
"xregexp": "5.0.2", "xregexp": "5.0.2",
"yup": "^0.32.11" "yup": "^0.32.11"
@ -165,6 +166,7 @@
"@types/i18n-js": "^3.8.2", "@types/i18n-js": "^3.8.2",
"@types/jest": "^26.0.24", "@types/jest": "^26.0.24",
"@types/lodash": "^4.14.171", "@types/lodash": "^4.14.171",
"@types/mocha": "^9.1.1",
"@types/react": "^17.0.14", "@types/react": "^17.0.14",
"@types/react-native": "0.68.1", "@types/react-native": "0.68.1",
"@types/react-native-background-timer": "^2.0.0", "@types/react-native-background-timer": "^2.0.0",
@ -205,6 +207,7 @@
"react-test-renderer": "17.0.2", "react-test-renderer": "17.0.2",
"reactotron-redux": "3.1.3", "reactotron-redux": "3.1.3",
"reactotron-redux-saga": "4.2.3", "reactotron-redux-saga": "4.2.3",
"ts-node": "^10.9.1",
"typescript": "^4.3.5" "typescript": "^4.3.5"
}, },
"jest": { "jest": {
@ -244,6 +247,7 @@
} }
}, },
"detox": { "detox": {
"test-runner": "mocha",
"runner-config": "e2e/.mocharc.json", "runner-config": "e2e/.mocharc.json",
"specs": "e2e/tests", "specs": "e2e/tests",
"configurations": { "configurations": {

View File

@ -41,3 +41,61 @@ index f9c858b..94ea188 100644
public abstract class NotificationManagerCompatFacade { public abstract class NotificationManagerCompatFacade {
public static NotificationManagerCompat from(@NonNull Context context) { public static NotificationManagerCompat from(@NonNull Context context) {
diff --git a/node_modules/react-native-notifications/lib/ios/RCTConvert+RNNotifications.h b/node_modules/react-native-notifications/lib/ios/RCTConvert+RNNotifications.h
index 8b2c269..8667351 100644
--- a/node_modules/react-native-notifications/lib/ios/RCTConvert+RNNotifications.h
+++ b/node_modules/react-native-notifications/lib/ios/RCTConvert+RNNotifications.h
@@ -1,5 +1,5 @@
#import <React/RCTConvert.h>
-@import UserNotifications;
+#import <UserNotifications/UserNotifications.h>
@interface RCTConvert (UIMutableUserNotificationAction)
+ (UIMutableUserNotificationAction *)UIMutableUserNotificationAction:(id)json;
diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.h b/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.h
index 4bc5292..4839d55 100644
--- a/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.h
+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.h
@@ -4,7 +4,7 @@ typedef void (^RCTPromiseResolveBlock)(id result);
typedef void (^RCTResponseSenderBlock)(NSArray *response);
typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error);
-@import UserNotifications;
+#import <UserNotifications/UserNotifications.h>
@interface RNNotificationCenter : NSObject
diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationEventHandler.h b/node_modules/react-native-notifications/lib/ios/RNNotificationEventHandler.h
index a07c6e9..8e3ca6a 100644
--- a/node_modules/react-native-notifications/lib/ios/RNNotificationEventHandler.h
+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationEventHandler.h
@@ -1,5 +1,5 @@
#import <Foundation/Foundation.h>
-@import UserNotifications;
+#import <UserNotifications/UserNotifications.h>
#import "RNNotificationsStore.h"
#import "RNEventEmitter.h"
diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationParser.h b/node_modules/react-native-notifications/lib/ios/RNNotificationParser.h
index 7aa2bfb..c1c019c 100644
--- a/node_modules/react-native-notifications/lib/ios/RNNotificationParser.h
+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationParser.h
@@ -1,5 +1,5 @@
#import <Foundation/Foundation.h>
-@import UserNotifications;
+#import <UserNotifications/UserNotifications.h>
@interface RNNotificationParser : NSObject
diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationsStore.h b/node_modules/react-native-notifications/lib/ios/RNNotificationsStore.h
index 4f8a171..7e4f9ca 100644
--- a/node_modules/react-native-notifications/lib/ios/RNNotificationsStore.h
+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationsStore.h
@@ -1,6 +1,6 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
-@import UserNotifications;
+#import <UserNotifications/UserNotifications.h>
@interface RNNotificationsStore : NSObject

407
yarn.lock
View File

@ -1324,16 +1324,7 @@
"@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9"
"@babel/plugin-proposal-optional-chaining" "^7.18.9" "@babel/plugin-proposal-optional-chaining" "^7.18.9"
"@babel/plugin-proposal-async-generator-functions@^7.17.12": "@babel/plugin-proposal-async-generator-functions@^7.0.0", "@babel/plugin-proposal-async-generator-functions@^7.18.10":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz#094a417e31ce7e692d84bab06c8e2a607cbeef03"
integrity sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/helper-remap-async-to-generator" "^7.16.8"
"@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/plugin-proposal-async-generator-functions@^7.18.10":
version "7.18.10" version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz#85ea478c98b0095c3e4102bff3b67d306ed24952" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz#85ea478c98b0095c3e4102bff3b67d306ed24952"
integrity sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew== integrity sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==
@ -1343,6 +1334,15 @@
"@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/helper-remap-async-to-generator" "^7.18.9"
"@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/plugin-proposal-async-generator-functions@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz#094a417e31ce7e692d84bab06c8e2a607cbeef03"
integrity sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/helper-remap-async-to-generator" "^7.16.8"
"@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/plugin-proposal-class-properties@^7.0.0": "@babel/plugin-proposal-class-properties@^7.0.0":
version "7.8.3" version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e"
@ -2344,6 +2344,14 @@
"@babel/helper-module-transforms" "^7.18.6" "@babel/helper-module-transforms" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-named-capturing-groups-regex@^7.0.0", "@babel/plugin-transform-named-capturing-groups-regex@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz#c89bfbc7cc6805d692f3a49bc5fc1b630007246d"
integrity sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==
dependencies:
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-named-capturing-groups-regex@^7.17.12": "@babel/plugin-transform-named-capturing-groups-regex@^7.17.12":
version "7.17.12" version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz#9c4a5a5966e0434d515f2675c227fd8cc8606931" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz#9c4a5a5966e0434d515f2675c227fd8cc8606931"
@ -2352,14 +2360,6 @@
"@babel/helper-create-regexp-features-plugin" "^7.17.12" "@babel/helper-create-regexp-features-plugin" "^7.17.12"
"@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz#c89bfbc7cc6805d692f3a49bc5fc1b630007246d"
integrity sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==
dependencies:
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-new-target@^7.17.12": "@babel/plugin-transform-new-target@^7.17.12":
version "7.17.12" version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.17.12.tgz#10842cd605a620944e81ea6060e9e65c265742e3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.17.12.tgz#10842cd605a620944e81ea6060e9e65c265742e3"
@ -3407,6 +3407,13 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
"@discoveryjs/json-ext@^0.5.3": "@discoveryjs/json-ext@^0.5.3":
version "0.5.7" version "0.5.7"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
@ -3572,26 +3579,26 @@
mv "~2" mv "~2"
safe-json-stringify "~1" safe-json-stringify "~1"
"@expo/cli@0.1.5": "@expo/cli@0.2.11":
version "0.1.5" version "0.2.11"
resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.1.5.tgz#2427e3c3b6be1936b2e6ffb595fc9c83e37e4be1" resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.2.11.tgz#25d8db8e46c6f02ef3edc189fdb6e29c922dd377"
integrity sha512-27LNT3b9MtBHEosmvJiC9Ug9aJpQAK9T3cC8ekaB9cHnVcJw+mJs2kdVBYpV1aBjKkH7T57aiWWimZp0O7m1wQ== integrity sha512-TIlylp3nghiEdlVliZRcBg8Yb++tnU92HinuQQZznVGFXFCoqJ210SPUJS1j3rxxltt8NhIJjL9OTO7PYRqnsQ==
dependencies: dependencies:
"@babel/runtime" "^7.14.0" "@babel/runtime" "^7.14.0"
"@expo/code-signing-certificates" "^0.0.2" "@expo/code-signing-certificates" "^0.0.2"
"@expo/config" "~6.0.23" "@expo/config" "~7.0.1"
"@expo/config-plugins" "~4.1.4" "@expo/config-plugins" "~5.0.1"
"@expo/dev-server" "~0.1.110" "@expo/dev-server" "~0.1.119"
"@expo/devcert" "^1.0.0" "@expo/devcert" "^1.0.0"
"@expo/json-file" "^8.2.35" "@expo/json-file" "^8.2.35"
"@expo/metro-config" "~0.3.16" "@expo/metro-config" "~0.3.18"
"@expo/osascript" "^2.0.31" "@expo/osascript" "^2.0.31"
"@expo/package-manager" "~0.0.52" "@expo/package-manager" "~0.0.53"
"@expo/plist" "^0.0.18" "@expo/plist" "^0.0.18"
"@expo/prebuild-config" "~4.0.0" "@expo/prebuild-config" "~5.0.3"
"@expo/rudder-sdk-node" "1.1.1" "@expo/rudder-sdk-node" "1.1.1"
"@expo/spawn-async" "1.5.0" "@expo/spawn-async" "1.5.0"
"@expo/xcpretty" "^4.1.1" "@expo/xcpretty" "^4.2.1"
"@urql/core" "2.3.6" "@urql/core" "2.3.6"
"@urql/exchange-retry" "0.3.0" "@urql/exchange-retry" "0.3.0"
accepts "^1.3.8" accepts "^1.3.8"
@ -3601,6 +3608,7 @@
cacache "^15.3.0" cacache "^15.3.0"
chalk "^4.0.0" chalk "^4.0.0"
ci-info "^3.3.0" ci-info "^3.3.0"
debug "^4.3.4"
env-editor "^0.4.1" env-editor "^0.4.1"
form-data "^3.0.1" form-data "^3.0.1"
freeport-async "2.0.0" freeport-async "2.0.0"
@ -3626,6 +3634,7 @@
requireg "^0.2.2" requireg "^0.2.2"
resolve-from "^5.0.0" resolve-from "^5.0.0"
semver "^6.3.0" semver "^6.3.0"
send "^0.18.0"
slugify "^1.3.4" slugify "^1.3.4"
structured-headers "^0.4.1" structured-headers "^0.4.1"
tar "^6.0.5" tar "^6.0.5"
@ -3644,7 +3653,7 @@
node-forge "^1.2.1" node-forge "^1.2.1"
nullthrows "^1.1.1" nullthrows "^1.1.1"
"@expo/config-plugins@4.1.5", "@expo/config-plugins@^4.0.14", "@expo/config-plugins@^4.1.5", "@expo/config-plugins@~4.1.4": "@expo/config-plugins@^4.0.14", "@expo/config-plugins@^4.1.5":
version "4.1.5" version "4.1.5"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-4.1.5.tgz#9d357d2cda9c095e511b51583ede8a3b76174068" resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-4.1.5.tgz#9d357d2cda9c095e511b51583ede8a3b76174068"
integrity sha512-RVvU40RtZt12HavuDAe+LDIq9lHj7sheOfMEHdmpJ/uTA8pgvkbc56XF6JHQD+yRr6+uhhb+JnAasGq49dsQbw== integrity sha512-RVvU40RtZt12HavuDAe+LDIq9lHj7sheOfMEHdmpJ/uTA8pgvkbc56XF6JHQD+yRr6+uhhb+JnAasGq49dsQbw==
@ -3665,7 +3674,7 @@
xcode "^3.0.1" xcode "^3.0.1"
xml2js "0.4.23" xml2js "0.4.23"
"@expo/config-plugins@~5.0.1": "@expo/config-plugins@~5.0.0", "@expo/config-plugins@~5.0.1":
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-5.0.1.tgz#66bc8d15785bdcd3598e466344f8c0518390179d" resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-5.0.1.tgz#66bc8d15785bdcd3598e466344f8c0518390179d"
integrity sha512-1OfnsOrfeSkB0VZfT01UjQ5Uq6p+yYbq8yNkj0e99K/6NLHpyvIxj+5tZIV0nQXgkOcqBIABL2uA7lwB8CkaBQ== integrity sha512-1OfnsOrfeSkB0VZfT01UjQ5Uq6p+yYbq8yNkj0e99K/6NLHpyvIxj+5tZIV0nQXgkOcqBIABL2uA7lwB8CkaBQ==
@ -3696,24 +3705,7 @@
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-46.0.2.tgz#191f225ebfcbe624868ddc40efae79593f948dd8" resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-46.0.2.tgz#191f225ebfcbe624868ddc40efae79593f948dd8"
integrity sha512-PXkmOgNwRyBfgVT1HmFZhfh3Qm7WKKyV6mk3/5HJ/LzPh1t+Zs2JrWX8U2YncTLV1QzV7nV8tnkyvszzqnZEzQ== integrity sha512-PXkmOgNwRyBfgVT1HmFZhfh3Qm7WKKyV6mk3/5HJ/LzPh1t+Zs2JrWX8U2YncTLV1QzV7nV8tnkyvszzqnZEzQ==
"@expo/config@6.0.24", "@expo/config@^6.0.14", "@expo/config@~6.0.23": "@expo/config@7.0.1", "@expo/config@~7.0.0", "@expo/config@~7.0.1":
version "6.0.24"
resolved "https://registry.yarnpkg.com/@expo/config/-/config-6.0.24.tgz#3602da8fdfa817e290a52fb328fc8ed9d6bc61e7"
integrity sha512-OcACI1md1Yo5TQmUxxueJ/RaTlR2Mgl6KswTFOYCL1XJERF/jjAx95zhWXH+JQGdlM0yB0vqM6vB6GbUFRvLxA==
dependencies:
"@babel/code-frame" "~7.10.4"
"@expo/config-plugins" "4.1.5"
"@expo/config-types" "^45.0.0"
"@expo/json-file" "8.2.36"
getenv "^1.0.0"
glob "7.1.6"
require-from-string "^2.0.2"
resolve-from "^5.0.0"
semver "7.3.2"
slugify "^1.3.4"
sucrase "^3.20.0"
"@expo/config@~7.0.0":
version "7.0.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/@expo/config/-/config-7.0.1.tgz#d8e2e5410bb0b8e305690bbc76e6bb76f6a6de31" resolved "https://registry.yarnpkg.com/@expo/config/-/config-7.0.1.tgz#d8e2e5410bb0b8e305690bbc76e6bb76f6a6de31"
integrity sha512-4lu0wr45XXJ2MXiLAm2+fmOyy/jjqF3NuDm92fO6nuulRzEEvTP4w3vsibJ690rT81ohtvhpruKhkRs0wSjKWA== integrity sha512-4lu0wr45XXJ2MXiLAm2+fmOyy/jjqF3NuDm92fO6nuulRzEEvTP4w3vsibJ690rT81ohtvhpruKhkRs0wSjKWA==
@ -3730,13 +3722,13 @@
slugify "^1.3.4" slugify "^1.3.4"
sucrase "^3.20.0" sucrase "^3.20.0"
"@expo/dev-server@~0.1.110": "@expo/dev-server@~0.1.119":
version "0.1.113" version "0.1.119"
resolved "https://registry.yarnpkg.com/@expo/dev-server/-/dev-server-0.1.113.tgz#db4a52af1817fbfbc9dbe52d8673ddc159d0b92c" resolved "https://registry.yarnpkg.com/@expo/dev-server/-/dev-server-0.1.119.tgz#d85036d8ddfd5668fd50ef373616b55580dc7670"
integrity sha512-PT3HT+3h4ZS1bw6Zz8fqjNeryKOWe1FqGdnz4RSASxZGCzib6VHLfLbJeYHkq7t+ashSXRoAw3XW/9yVdbUqLA== integrity sha512-DcVnj4/YA+b+Ljsz2qffHHN5LbouXFKeE9ER0Yjq5vIb2moV1q3U6LezndFLCf42Uev7C2vSa8YCcP3WOpxuMw==
dependencies: dependencies:
"@expo/bunyan" "4.0.0" "@expo/bunyan" "4.0.0"
"@expo/metro-config" "0.3.18" "@expo/metro-config" "~0.3.18"
"@expo/osascript" "2.0.33" "@expo/osascript" "2.0.33"
body-parser "1.19.0" body-parser "1.19.0"
chalk "^4.0.0" chalk "^4.0.0"
@ -3794,12 +3786,12 @@
json5 "^1.0.1" json5 "^1.0.1"
write-file-atomic "^2.3.0" write-file-atomic "^2.3.0"
"@expo/metro-config@0.3.18", "@expo/metro-config@~0.3.16": "@expo/metro-config@~0.3.18":
version "0.3.18" version "0.3.22"
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.3.18.tgz#72705b3a0a3fb863b1a068f2b5f4cb43828cb26b" resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.3.22.tgz#fa4a0729ec8ecbc9c9fb79c63ecc66a299505c82"
integrity sha512-DWtwV67kD8X2uOKIs5QyHlHD+6L6RAgudZZDBmu433ZvL62HAUYfjEi3+i0jeMiUqN85o1vbXg6xqWnBCpS50g== integrity sha512-R81sLbaeUBjN8IXcxiVx7GcpSj8z7szILl1b5yJDb38WdIFwxhrseA5wXaTT1yMhI+59w6n99T2qtFV2yD5qYA==
dependencies: dependencies:
"@expo/config" "6.0.24" "@expo/config" "7.0.1"
"@expo/json-file" "8.2.36" "@expo/json-file" "8.2.36"
chalk "^4.1.0" chalk "^4.1.0"
debug "^4.3.2" debug "^4.3.2"
@ -3816,10 +3808,10 @@
"@expo/spawn-async" "^1.5.0" "@expo/spawn-async" "^1.5.0"
exec-async "^2.2.0" exec-async "^2.2.0"
"@expo/package-manager@~0.0.52": "@expo/package-manager@~0.0.53":
version "0.0.54" version "0.0.56"
resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-0.0.54.tgz#b56254d06b8a1476dddb2c59be1c0f7b192bab22" resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-0.0.56.tgz#214a8db48752cde968827c20c5b54a88187b5422"
integrity sha512-Sr7UsDh9Pcta1gAFZJgszodewEvg/XSRV1oV+iTrkUEhP7NziMrK5dE71O2FHmKGfdrDQgLexvq8HLZdfRskKw== integrity sha512-PGk34uz4XDyhoNIlPh2D+BDsiXYuW2jXavTiax8d32uvHlRO6FN0cAsqlWD6fx3H2hRn8cU/leTuc4M7pYovCQ==
dependencies: dependencies:
"@expo/json-file" "8.2.36" "@expo/json-file" "8.2.36"
"@expo/spawn-async" "^1.5.0" "@expo/spawn-async" "^1.5.0"
@ -3841,18 +3833,17 @@
base64-js "^1.2.3" base64-js "^1.2.3"
xmlbuilder "^14.0.0" xmlbuilder "^14.0.0"
"@expo/prebuild-config@~4.0.0": "@expo/prebuild-config@~5.0.3":
version "4.0.2" version "5.0.3"
resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-4.0.2.tgz#f6317a7b88cf6eec777c2547e88660c63f153223" resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-5.0.3.tgz#f475797a592f074b5a66f02aef27c6c14c54591e"
integrity sha512-+AQ/EVgcySl3cvYMmZLaEyGkxvQnO+UFU2mshmUoUh5lTIFTNKl1aVo0UmYW2/JehmKu6bxOrr/lL5byHv+fcQ== integrity sha512-G4j1H3WFjRaiQ+FgFNULrnIm7RsQyjc4xp6lLTP2ydBv79wO3x8wAdeZvaZh7eOkfu9BESpQzACT1uuJTag5jg==
dependencies: dependencies:
"@expo/config" "6.0.24" "@expo/config" "7.0.1"
"@expo/config-plugins" "4.1.5" "@expo/config-plugins" "~5.0.1"
"@expo/config-types" "^45.0.0" "@expo/config-types" "^46.0.0"
"@expo/image-utils" "0.3.20" "@expo/image-utils" "0.3.20"
"@expo/json-file" "8.2.36" "@expo/json-file" "8.2.36"
debug "^4.3.1" debug "^4.3.1"
expo-modules-autolinking "0.8.1"
fs-extra "^9.0.0" fs-extra "^9.0.0"
resolve-from "^5.0.0" resolve-from "^5.0.0"
semver "7.3.2" semver "7.3.2"
@ -3895,10 +3886,10 @@
resolved "https://registry.yarnpkg.com/@expo/vector-icons/-/vector-icons-13.0.0.tgz#e2989b85e95a82bce216f88cf8fb583ab050ec95" resolved "https://registry.yarnpkg.com/@expo/vector-icons/-/vector-icons-13.0.0.tgz#e2989b85e95a82bce216f88cf8fb583ab050ec95"
integrity sha512-TI+l71+5aSKnShYclFa14Kum+hQMZ86b95SH6tQUG3qZEmLTarvWpKwqtTwQKqvlJSJrpFiSFu3eCuZokY6zWA== integrity sha512-TI+l71+5aSKnShYclFa14Kum+hQMZ86b95SH6tQUG3qZEmLTarvWpKwqtTwQKqvlJSJrpFiSFu3eCuZokY6zWA==
"@expo/xcpretty@^4.1.1": "@expo/xcpretty@^4.2.1":
version "4.1.3" version "4.2.2"
resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-4.1.3.tgz#cbe04f654571c0ee733dbe729d5593b3bcfbf487" resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-4.2.2.tgz#7890f86b017015be8a20242ae74fe6ed4b80a92c"
integrity sha512-testj0jpEe1IwRfnmQ3shizTXOY6IGcuMJg1vtmXy2bC9sPTLK1wjliRJp2xCJGcp1ZbEA1/eptzX+6MDnYjrA== integrity sha512-Lke/geldJqUV0Dfxg5/QIOugOzdqZ/rQ9yHKSgGbjZtG1uiSqWyFwWvXmrdd3/sIdX33eykGvIcf+OrvvcXVUw==
dependencies: dependencies:
"@babel/code-frame" "7.10.4" "@babel/code-frame" "7.10.4"
chalk "^4.1.0" chalk "^4.1.0"
@ -4604,7 +4595,7 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec"
integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==
"@jridgewell/trace-mapping@^0.3.0": "@jridgewell/trace-mapping@0.3.9", "@jridgewell/trace-mapping@^0.3.0":
version "0.3.9" version "0.3.9"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
@ -5841,6 +5832,26 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@tsconfig/node10@^1.0.7":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
"@tsconfig/node12@^1.0.7":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
"@tsconfig/node14@^1.0.0":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
"@tsconfig/node16@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
"@types/anymatch@*": "@types/anymatch@*":
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
@ -6124,6 +6135,11 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/mocha@^9.1.1":
version "9.1.1"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4"
integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==
"@types/node-fetch@^2.5.7": "@types/node-fetch@^2.5.7":
version "2.6.2" version "2.6.2"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"
@ -6762,6 +6778,11 @@ acorn-jsx@^5.3.1:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^6.4.1: acorn@^6.4.1:
version "6.4.1" version "6.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
@ -6772,7 +6793,7 @@ acorn@^7.4.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
acorn@^8.5.0: acorn@^8.4.1, acorn@^8.5.0:
version "8.8.0" version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@ -7049,6 +7070,11 @@ arg@4.1.0:
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.0.tgz#583c518199419e0037abb74062c37f8519e575f0" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.0.tgz#583c518199419e0037abb74062c37f8519e575f0"
integrity sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg== integrity sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
argparse@^1.0.7: argparse@^1.0.7:
version "1.0.10" version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@ -7544,10 +7570,10 @@ babel-plugin-react-docgen@^4.2.1:
lodash "^4.17.15" lodash "^4.17.15"
react-docgen "^5.0.0" react-docgen "^5.0.0"
babel-plugin-react-native-web@~0.17.1: babel-plugin-react-native-web@~0.18.2:
version "0.17.7" version "0.18.7"
resolved "https://registry.yarnpkg.com/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.17.7.tgz#1580e27a2e3c6692127535d3880fe1e247ef6414" resolved "https://registry.yarnpkg.com/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.18.7.tgz#d32750aca96e5518122504338196502034393261"
integrity sha512-UBLfIsfU3vi//Ab4i0WSWAfm1whLTK9uJoH0RPZ6a67eS/h9JGYjKy7+1RpHxSBviHi9NIMiYfWseTLjyIsE1g== integrity sha512-DF7huAePyphXsqWhGyshjQAU9qektOqOSP2NHevtUBhsgLu57D4gEGZM1xPtbJYvW6/DoxuaXUAqjYqfexT+gQ==
babel-plugin-require-context-hook@^1.0.0: babel-plugin-require-context-hook@^1.0.0:
version "1.0.0" version "1.0.0"
@ -7592,17 +7618,17 @@ babel-preset-current-node-syntax@^1.0.0:
"@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3"
"@babel/plugin-syntax-top-level-await" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3"
babel-preset-expo@~9.1.0: babel-preset-expo@~9.2.0:
version "9.1.0" version "9.2.0"
resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-9.1.0.tgz#4cbac7d28618bb68bc9c2a0e7dccda7b207b61ab" resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-9.2.0.tgz#d01793e3a556065f103b3095fbbc959d52f08e88"
integrity sha512-dFcgT7AY5n15bLnfOM6R25f8Lh7YSALj4zeGze6aspYHfVrREYcovVG0eMGpY9V24fnwByNRv85lElc1jAj1Mw== integrity sha512-aM2htiNx0H49H+MWCp9+cKVSdcdNSn0tbE5Dln/GO1xna4ZlnA30clbfClcYJFUcZtW90IsYeZwQ/hj8zyWhNA==
dependencies: dependencies:
"@babel/plugin-proposal-decorators" "^7.12.9" "@babel/plugin-proposal-decorators" "^7.12.9"
"@babel/plugin-transform-react-jsx" "^7.12.17" "@babel/plugin-transform-react-jsx" "^7.12.17"
"@babel/preset-env" "^7.12.9" "@babel/preset-env" "^7.12.9"
babel-plugin-module-resolver "^4.1.0" babel-plugin-module-resolver "^4.1.0"
babel-plugin-react-native-web "~0.17.1" babel-plugin-react-native-web "~0.18.2"
metro-react-native-babel-preset "~0.67.0" metro-react-native-babel-preset "~0.70.3"
babel-preset-fbjs@^3.4.0: babel-preset-fbjs@^3.4.0:
version "3.4.0" version "3.4.0"
@ -9146,6 +9172,11 @@ create-react-context@0.3.0:
gud "^1.0.0" gud "^1.0.0"
warning "^4.0.3" warning "^4.0.3"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-fetch@^3.0.4: cross-fetch@^3.0.4:
version "3.1.5" version "3.1.5"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
@ -9626,6 +9657,11 @@ diff@5.0.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
diffie-hellman@^5.0.0: diffie-hellman@^5.0.0:
version "5.0.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@ -10582,17 +10618,19 @@ expo-apple-authentication@4.2.1:
dependencies: dependencies:
"@expo/config-plugins" "^4.0.14" "@expo/config-plugins" "^4.0.14"
expo-application@~4.1.0: expo-application@~4.2.2:
version "4.1.0" version "4.2.2"
resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-4.1.0.tgz#e0214ff7cf73db5a5e97e609ffbab3cc98288030" resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-4.2.2.tgz#c9500819723c59eaee5ca9832bf17d1fd4139f74"
integrity sha512-Z2kctgVMpYZB1Iwaxd+XcMBq7h8EEY50GGrwxXsb1OHHQKN+WEVGBWxjvtPkAroqCdujLaB5HBay46gvUHRDQg== integrity sha512-bFEaFRUdV6aK2iBd+HzkHNPYsyj88EAhaQW5leznmO0qQMJxpAQ3eoUXMey1hfDBh1qgkkHgSyCZ9BIgMAGJ1g==
expo-asset@~8.5.0: expo-asset@~8.6.1:
version "8.5.0" version "8.6.1"
resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-8.5.0.tgz#d83ed8e42f1aa3d74aeca67b87c90e17f1661b0f" resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-8.6.1.tgz#86355b3e231e8aa6cf68a456ce9746dff1478b48"
integrity sha512-k3QErZYxb6e6rPkJ1sG5yIJ7bhd4RFvnFStz0ZCO6SfktGygBAjTz5aTOLaaomiCIObRiBQ4byky/RLdli/NLw== integrity sha512-urbUp1YtwH2J0Qc3inGQJdqTjWKML77SeMNgff+iR9MUE8gDkFqSCDjrBi7i5Oj5DDtq43mmtDg8G8ei6Vchcg==
dependencies: dependencies:
blueimp-md5 "^2.10.0" blueimp-md5 "^2.10.0"
expo-constants "~13.2.2"
expo-file-system "~14.1.0"
invariant "^2.2.4" invariant "^2.2.4"
md5-file "^3.2.3" md5-file "^3.2.3"
path-browserify "^1.0.0" path-browserify "^1.0.0"
@ -10605,20 +10643,20 @@ expo-av@11.2.3:
dependencies: dependencies:
"@expo/config-plugins" "^4.0.14" "@expo/config-plugins" "^4.0.14"
expo-constants@~13.1.1: expo-constants@~13.2.2, expo-constants@~13.2.4:
version "13.1.1" version "13.2.4"
resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-13.1.1.tgz#78c26c760cc63cd5608bc4b51bd159d7339d8054" resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-13.2.4.tgz#eab4a553f074b2c60ad7a158d3b82e3484a94606"
integrity sha512-QRVHrrMCLenBzWZ8M+EvCXM+jjdQzFMW27YQHRac3SGGoND1hWr81scOmGwlFo2wLZrYXm8HcYt1E6ry3IIwrA== integrity sha512-Zobau8EuTk2GgafwkfGnWM6CmSLB7X8qnQXVuXe0nd3v92hfQUmRWGhJwH88uxXj3LrfqctM6PaJ8taG1vxfBw==
dependencies: dependencies:
"@expo/config" "^6.0.14" "@expo/config" "~7.0.0"
uuid "^3.3.2" uuid "^3.3.2"
expo-error-recovery@~3.1.0: expo-error-recovery@~3.2.0:
version "3.1.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/expo-error-recovery/-/expo-error-recovery-3.1.0.tgz#c841772e11ed55180e30ebf809580ab051b70535" resolved "https://registry.yarnpkg.com/expo-error-recovery/-/expo-error-recovery-3.2.0.tgz#3a4543382904a5e70829cb41d7fc0f022c2bef6e"
integrity sha512-qUxCW7kPB6AVX5h3ZPVnxw4LLZWsRwAPBtRDlh1UDN7GWZ+CQN1SNk0w0BPotjNtSlXEZSFDqKqtoDDAUYjNmg== integrity sha512-XZ630ks5HNxa9oc2Ya1hEn1ez031Cy4VnyxerPC2o9fKNKSrD/64cRqGF9NkGM3X2uf8+PCB9adxVflAIXBf6w==
expo-file-system@14.0.0, expo-file-system@~14.0.0: expo-file-system@14.0.0:
version "14.0.0" version "14.0.0"
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-14.0.0.tgz#8367af10969a486fcba2f1e1c7cc0148f855e962" resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-14.0.0.tgz#8367af10969a486fcba2f1e1c7cc0148f855e962"
integrity sha512-Asva7ehLUq/PIem6Y+/OQvoIqhFqYDd7l4l49yDRDgLSbK2I7Fr8qGhDeDpnUXrMVamg2uwt9zRGhyrjFNRhVw== integrity sha512-Asva7ehLUq/PIem6Y+/OQvoIqhFqYDd7l4l49yDRDgLSbK2I7Fr8qGhDeDpnUXrMVamg2uwt9zRGhyrjFNRhVw==
@ -10626,10 +10664,18 @@ expo-file-system@14.0.0, expo-file-system@~14.0.0:
"@expo/config-plugins" "^4.0.14" "@expo/config-plugins" "^4.0.14"
uuid "^3.4.0" uuid "^3.4.0"
expo-font@~10.1.0: expo-file-system@~14.1.0:
version "10.1.0" version "14.1.0"
resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-10.1.0.tgz#2e8f8954943c5afca8444c1ffb1d74623c6a4fb6" resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-14.1.0.tgz#4fa410873ef12ac8bec873593f7489f4305a14b8"
integrity sha512-vmhzpE95Ym4iOj8IELof+C/3Weert2B3LyxV5rBjGosjzBdov+o+S6b5mN7Yc9kyEGykwB6k7npL45X3hFYDQA== integrity sha512-lJcPGQ8yKXVknVkD5TmcJnR/TpQbEL0JP8hknLejfq3FIqPqI/LBFn31YiP37grxW8lITz1al8pq5T6CSUjAzQ==
dependencies:
"@expo/config-plugins" "~5.0.0"
uuid "^3.4.0"
expo-font@~10.2.0:
version "10.2.0"
resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-10.2.0.tgz#881f767e13b2b534a4d3ffaedcf675ce6b63439d"
integrity sha512-2V4EcpmhNoppaLn+lPprZVS+3bmV9hxLPKttKh2u8ghjH/oX9bv3u4JVo77SYh0EfrWO4toqVyXn8pXH8GpbIg==
dependencies: dependencies:
fontfaceobserver "^2.1.0" fontfaceobserver "^2.1.0"
@ -10638,11 +10684,16 @@ expo-haptics@11.2.0:
resolved "https://registry.yarnpkg.com/expo-haptics/-/expo-haptics-11.2.0.tgz#0ffb9f82395e88f9f66ceebb0f3279739311412c" resolved "https://registry.yarnpkg.com/expo-haptics/-/expo-haptics-11.2.0.tgz#0ffb9f82395e88f9f66ceebb0f3279739311412c"
integrity sha512-ijuWU2ljLBGjIf7OQCvnBQIu/chezndnWkfi518XxvK0hudA4+fAe98mqHO6hom9GexNRxhQZbXc0hcVnxkaiA== integrity sha512-ijuWU2ljLBGjIf7OQCvnBQIu/chezndnWkfi518XxvK0hudA4+fAe98mqHO6hom9GexNRxhQZbXc0hcVnxkaiA==
expo-keep-awake@10.1.1, expo-keep-awake@~10.1.1: expo-keep-awake@10.1.1:
version "10.1.1" version "10.1.1"
resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-10.1.1.tgz#03023c130f7e3824b738e3fdd5353b8a2c0c1980" resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-10.1.1.tgz#03023c130f7e3824b738e3fdd5353b8a2c0c1980"
integrity sha512-9zC0sdhQljUeMr2yQ7o4kzEZXVAy82fFOAZE1+TwPL7qR0b0sphe7OJ5T1GX1qLcwuVaJ8YewaPoLSHRk79+Rg== integrity sha512-9zC0sdhQljUeMr2yQ7o4kzEZXVAy82fFOAZE1+TwPL7qR0b0sphe7OJ5T1GX1qLcwuVaJ8YewaPoLSHRk79+Rg==
expo-keep-awake@~10.2.0:
version "10.2.0"
resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-10.2.0.tgz#46f04740bccd321732bbbed93491e2076d5dbbd7"
integrity sha512-kIRtO4Hmrvxh4E45IPWG/NiUZsuRe1AQwBT09pq+kx8nm6tUS4B9TeL6+1NFy+qVBLbGKDqoQD5Ez7XYTFtBeQ==
expo-local-authentication@12.2.0: expo-local-authentication@12.2.0:
version "12.2.0" version "12.2.0"
resolved "https://registry.yarnpkg.com/expo-local-authentication/-/expo-local-authentication-12.2.0.tgz#a1534d2f1a3e63483d20a1cff303b1a312b8e1ef" resolved "https://registry.yarnpkg.com/expo-local-authentication/-/expo-local-authentication-12.2.0.tgz#a1534d2f1a3e63483d20a1cff303b1a312b8e1ef"
@ -10651,10 +10702,10 @@ expo-local-authentication@12.2.0:
"@expo/config-plugins" "^4.0.14" "@expo/config-plugins" "^4.0.14"
invariant "^2.2.4" invariant "^2.2.4"
expo-modules-autolinking@0.8.1: expo-modules-autolinking@0.10.3:
version "0.8.1" version "0.10.3"
resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-0.8.1.tgz#533c38192847d2272e9af986f8f4c58aae6dfff3" resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-0.10.3.tgz#31bfcf3e4b613a7c3949fb1f1e9c23eea4c14caf"
integrity sha512-S8qfaXCv//7tQWV9M+JKx3CF7ypYhDdSUbkUQdaVO/r8D76/aRTArY/aRw1yEfaAOzyK8C8diDToV1itl51DfQ== integrity sha512-av9ln2zwUt303g98raX7sDmESgL3SXs1sbbtIjh1rL7R0676XIUacIKgbydR0/4tMbOShWx14Z9fozpk9xIAJA==
dependencies: dependencies:
chalk "^4.1.0" chalk "^4.1.0"
commander "^7.2.0" commander "^7.2.0"
@ -10662,10 +10713,10 @@ expo-modules-autolinking@0.8.1:
find-up "^5.0.0" find-up "^5.0.0"
fs-extra "^9.1.0" fs-extra "^9.1.0"
expo-modules-core@0.9.2: expo-modules-core@0.11.4:
version "0.9.2" version "0.11.4"
resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-0.9.2.tgz#657a3d804e73f3d41e6fa35d40a44aee5a4a287e" resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-0.11.4.tgz#6b7a27bb212f3fbf7d6803f747f6491aa73a2a09"
integrity sha512-p/C0GJxFIIDGwmrWi70Q0ggfsgeUFS25ZkkBgoaHT7MVgiMjlKA/DCC3D6ZUkHl/JlzUm0aTftIGS8LWXsnZBw== integrity sha512-8dEYICk7hUi1GPz5hWm8dBuZDGc+4Tm7zDhSIhKApo5jY/5vB4Bk+fjPo693iWn6pp3+XBHT8Ri8rJ3G7wH1vQ==
dependencies: dependencies:
compare-versions "^3.4.0" compare-versions "^3.4.0"
invariant "^2.2.4" invariant "^2.2.4"
@ -10682,24 +10733,24 @@ expo-web-browser@10.2.1:
dependencies: dependencies:
compare-urls "^2.0.0" compare-urls "^2.0.0"
expo@^45.0.5: expo@^46.0.9:
version "45.0.5" version "46.0.9"
resolved "https://registry.yarnpkg.com/expo/-/expo-45.0.5.tgz#ff99ad44a59ffabf473c43abbff35d17b10862fe" resolved "https://registry.yarnpkg.com/expo/-/expo-46.0.9.tgz#4b4b943343c45c3a05c71da49c1cfd5555ab5f85"
integrity sha512-ND+Fo/iLZK1ubMvPFzraIQBvtGL7a4ZHGIP8N1PjcOtTGrCc6X7IWyLkfPMAck2yhd80ZTbos8vTU3SAUuBcJw== integrity sha512-UsBjm0BL7w+OyF6kypVPrk3jhg9cCXF0D9CaOWQ+cedm7oT4mTVQx9+A45VsDvLzNWBjJejZQZ1PFKqOY5HNcQ==
dependencies: dependencies:
"@babel/runtime" "^7.14.0" "@babel/runtime" "^7.14.0"
"@expo/cli" "0.1.5" "@expo/cli" "0.2.11"
"@expo/vector-icons" "^13.0.0" "@expo/vector-icons" "^13.0.0"
babel-preset-expo "~9.1.0" babel-preset-expo "~9.2.0"
cross-spawn "^6.0.5" cross-spawn "^6.0.5"
expo-application "~4.1.0" expo-application "~4.2.2"
expo-asset "~8.5.0" expo-asset "~8.6.1"
expo-constants "~13.1.1" expo-constants "~13.2.4"
expo-file-system "~14.0.0" expo-file-system "~14.1.0"
expo-font "~10.1.0" expo-font "~10.2.0"
expo-keep-awake "~10.1.1" expo-keep-awake "~10.2.0"
expo-modules-autolinking "0.8.1" expo-modules-autolinking "0.10.3"
expo-modules-core "0.9.2" expo-modules-core "0.11.4"
fbemitter "^3.0.0" fbemitter "^3.0.0"
getenv "^1.0.0" getenv "^1.0.0"
invariant "^2.2.4" invariant "^2.2.4"
@ -10708,7 +10759,7 @@ expo@^45.0.5:
pretty-format "^26.5.2" pretty-format "^26.5.2"
uuid "^3.4.0" uuid "^3.4.0"
optionalDependencies: optionalDependencies:
expo-error-recovery "~3.1.0" expo-error-recovery "~3.2.0"
express@^4.17.1: express@^4.17.1:
version "4.18.1" version "4.18.1"
@ -14279,6 +14330,11 @@ make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0:
dependencies: dependencies:
semver "^6.0.0" semver "^6.0.0"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
makeerror@1.0.12: makeerror@1.0.12:
version "1.0.12" version "1.0.12"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
@ -14575,7 +14631,7 @@ metro-minify-uglify@0.67.0:
dependencies: dependencies:
uglify-es "^3.1.9" uglify-es "^3.1.9"
metro-react-native-babel-preset@0.67.0, metro-react-native-babel-preset@^0.67.0, metro-react-native-babel-preset@~0.67.0: metro-react-native-babel-preset@0.67.0, metro-react-native-babel-preset@^0.67.0:
version "0.67.0" version "0.67.0"
resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.67.0.tgz#53aec093f53a09b56236a9bb534d76658efcbec7" resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.67.0.tgz#53aec093f53a09b56236a9bb534d76658efcbec7"
integrity sha512-tgTG4j0SKwLHbLRELMmgkgkjV1biYkWlGGKOmM484/fJC6bpDikdaFhfjsyE+W+qt7I5szbCPCickMTNQ+zwig== integrity sha512-tgTG4j0SKwLHbLRELMmgkgkjV1biYkWlGGKOmM484/fJC6bpDikdaFhfjsyE+W+qt7I5szbCPCickMTNQ+zwig==
@ -14621,6 +14677,51 @@ metro-react-native-babel-preset@0.67.0, metro-react-native-babel-preset@^0.67.0,
"@babel/template" "^7.0.0" "@babel/template" "^7.0.0"
react-refresh "^0.4.0" react-refresh "^0.4.0"
metro-react-native-babel-preset@~0.70.3:
version "0.70.3"
resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz#1c77ec4544ecd5fb6c803e70b21284d7483e4842"
integrity sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==
dependencies:
"@babel/core" "^7.14.0"
"@babel/plugin-proposal-async-generator-functions" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"
"@babel/plugin-proposal-export-default-from" "^7.0.0"
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0"
"@babel/plugin-proposal-object-rest-spread" "^7.0.0"
"@babel/plugin-proposal-optional-catch-binding" "^7.0.0"
"@babel/plugin-proposal-optional-chaining" "^7.0.0"
"@babel/plugin-syntax-dynamic-import" "^7.0.0"
"@babel/plugin-syntax-export-default-from" "^7.0.0"
"@babel/plugin-syntax-flow" "^7.2.0"
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0"
"@babel/plugin-syntax-optional-chaining" "^7.0.0"
"@babel/plugin-transform-arrow-functions" "^7.0.0"
"@babel/plugin-transform-async-to-generator" "^7.0.0"
"@babel/plugin-transform-block-scoping" "^7.0.0"
"@babel/plugin-transform-classes" "^7.0.0"
"@babel/plugin-transform-computed-properties" "^7.0.0"
"@babel/plugin-transform-destructuring" "^7.0.0"
"@babel/plugin-transform-exponentiation-operator" "^7.0.0"
"@babel/plugin-transform-flow-strip-types" "^7.0.0"
"@babel/plugin-transform-function-name" "^7.0.0"
"@babel/plugin-transform-literals" "^7.0.0"
"@babel/plugin-transform-modules-commonjs" "^7.0.0"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0"
"@babel/plugin-transform-parameters" "^7.0.0"
"@babel/plugin-transform-react-display-name" "^7.0.0"
"@babel/plugin-transform-react-jsx" "^7.0.0"
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
"@babel/plugin-transform-runtime" "^7.0.0"
"@babel/plugin-transform-shorthand-properties" "^7.0.0"
"@babel/plugin-transform-spread" "^7.0.0"
"@babel/plugin-transform-sticky-regex" "^7.0.0"
"@babel/plugin-transform-template-literals" "^7.0.0"
"@babel/plugin-transform-typescript" "^7.5.0"
"@babel/plugin-transform-unicode-regex" "^7.0.0"
"@babel/template" "^7.0.0"
react-refresh "^0.4.0"
metro-react-native-babel-transformer@0.67.0, metro-react-native-babel-transformer@^0.67.0: metro-react-native-babel-transformer@0.67.0, metro-react-native-babel-transformer@^0.67.0:
version "0.67.0" version "0.67.0"
resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.67.0.tgz#756d32eb3c05cab3d72fcb1700f8fd09322bb07f" resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.67.0.tgz#756d32eb3c05cab3d72fcb1700f8fd09322bb07f"
@ -17111,7 +17212,7 @@ react-native-navigation-bar-color@2.0.1:
resolved "https://registry.yarnpkg.com/react-native-navigation-bar-color/-/react-native-navigation-bar-color-2.0.1.tgz#ee2be25cc37105f7da355717b0a9a32c9c059ae6" resolved "https://registry.yarnpkg.com/react-native-navigation-bar-color/-/react-native-navigation-bar-color-2.0.1.tgz#ee2be25cc37105f7da355717b0a9a32c9c059ae6"
integrity sha512-1kE/oxWt+HYjRxdZdvke9tJ365xaee5n3+euOQA1En8zQuSbOxiE4SYEGM7TeaWnmLJ0l37mRnPHaB2H4mGh0A== integrity sha512-1kE/oxWt+HYjRxdZdvke9tJ365xaee5n3+euOQA1En8zQuSbOxiE4SYEGM7TeaWnmLJ0l37mRnPHaB2H4mGh0A==
react-native-notifications@^4.2.4: react-native-notifications@4.2.4:
version "4.2.4" version "4.2.4"
resolved "https://registry.yarnpkg.com/react-native-notifications/-/react-native-notifications-4.2.4.tgz#0d686eb1576d3d9cb73dd9db1ee4f212e00f7d89" resolved "https://registry.yarnpkg.com/react-native-notifications/-/react-native-notifications-4.2.4.tgz#0d686eb1576d3d9cb73dd9db1ee4f212e00f7d89"
integrity sha512-ffToxERa2bRUsXShCO19yXY6c6l4Esq7MqRKAb4mPSn+T428X7Je7WYvWOIOVw/BMGJ3R0lPrZk52vDpoYqanw== integrity sha512-ffToxERa2bRUsXShCO19yXY6c6l4Esq7MqRKAb4mPSn+T428X7Je7WYvWOIOVw/BMGJ3R0lPrZk52vDpoYqanw==
@ -18222,7 +18323,7 @@ semver@^7.3.4, semver@^7.3.7:
dependencies: dependencies:
lru-cache "^6.0.0" lru-cache "^6.0.0"
send@0.18.0: send@0.18.0, send@^0.18.0:
version "0.18.0" version "0.18.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
@ -19589,6 +19690,25 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
ts-node@^10.9.1:
version "10.9.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
dependencies:
"@cspotcode/source-map-support" "^0.8.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.2"
acorn "^8.4.1"
acorn-walk "^8.1.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
ts-pnp@^1.1.6: ts-pnp@^1.1.6:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
@ -20093,6 +20213,11 @@ use-composed-ref@^1.3.0:
resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda"
integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==
use-debounce@^8.0.4:
version "8.0.4"
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-8.0.4.tgz#27e93b2f010bd0b8ad06e9fc7de891d9ee5d6b8e"
integrity sha512-fGqsYQzl8kLHF2QpQSgIwgOgJmnh6j5L6SIzQiHdLfwp3q1egUL3btq5Bg2SJysH6A0ILLgT2IqXZKoNJr0nFw==
use-deep-compare-effect@1.6.1: use-deep-compare-effect@1.6.1:
version "1.6.1" version "1.6.1"
resolved "https://registry.yarnpkg.com/use-deep-compare-effect/-/use-deep-compare-effect-1.6.1.tgz#061a0ac5400aa0461e33dddfaa2a98bca873182a" resolved "https://registry.yarnpkg.com/use-deep-compare-effect/-/use-deep-compare-effect-1.6.1.tgz#061a0ac5400aa0461e33dddfaa2a98bca873182a"
@ -20212,6 +20337,11 @@ uuid@^8.0.0, uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
v8-compile-cache@^2.0.3: v8-compile-cache@^2.0.3:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
@ -20827,6 +20957,11 @@ yargs@^17.3.1:
y18n "^5.0.5" y18n "^5.0.5"
yargs-parser "^21.0.0" yargs-parser "^21.0.0"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
yocto-queue@^0.1.0: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"