Merge 4.26.2 into single-server (#4001)

This commit is contained in:
Diego Mello 2022-03-31 10:04:21 -03:00
parent 9d2175485c
commit b3c37ede7e
54 changed files with 987 additions and 511 deletions

View File

@ -1,45 +1,8 @@
import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots'; import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots';
import { render } from '@testing-library/react-native'; import { render } from '@testing-library/react-native';
jest.mock('rn-fetch-blob', () => ({
fs: {
dirs: {
DocumentDir: '/data/com.rocket.chat/documents',
DownloadDir: '/data/com.rocket.chat/downloads'
},
exists: jest.fn(() => null)
},
fetch: jest.fn(() => null),
config: jest.fn(() => null)
}));
jest.mock('react-native-file-viewer', () => ({
open: jest.fn(() => null)
}));
jest.mock('../app/lib/database', () => jest.fn(() => null));
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime()); global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
jest.mock('react-native-mmkv-storage', () => {
return {
Loader: jest.fn().mockImplementation(() => {
return {
setProcessingMode: jest.fn().mockImplementation(() => {
return {
withEncryption: jest.fn().mockImplementation(() => {
return {
initialize: jest.fn()
};
})
};
})
};
}),
create: jest.fn(),
MODES: { MULTI_PROCESS: '' }
};
});
const converter = new Stories2SnapsConverter(); const converter = new Stories2SnapsConverter();
initStoryshots({ initStoryshots({

View File

@ -144,7 +144,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.26.1" versionName "4.26.2"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
if (!isFoss) { if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]

View File

@ -8,6 +8,7 @@ import { avatarURL } from '../../utils/avatar';
import { SubscriptionType } from '../../definitions/ISubscription'; import { SubscriptionType } from '../../definitions/ISubscription';
import Emoji from '../markdown/Emoji'; import Emoji from '../markdown/Emoji';
import { IAvatar } from './interfaces'; import { IAvatar } from './interfaces';
import { useTheme } from '../../theme';
const Avatar = React.memo( const Avatar = React.memo(
({ ({
@ -18,7 +19,6 @@ const Avatar = React.memo(
user, user,
onPress, onPress,
emoji, emoji,
theme,
getCustomEmoji, getCustomEmoji,
avatarETag, avatarETag,
isStatic, isStatic,
@ -34,6 +34,8 @@ const Avatar = React.memo(
return null; return null;
} }
const { theme } = useTheme();
const avatarStyle = { const avatarStyle = {
width: size, width: size,
height: size, height: size,
@ -44,7 +46,7 @@ const Avatar = React.memo(
if (emoji) { if (emoji) {
image = ( image = (
<Emoji <Emoji
theme={theme!} theme={theme}
baseUrl={server} baseUrl={server}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
isMessageContainsOnlyEmoji isMessageContainsOnlyEmoji

View File

@ -5,13 +5,11 @@ import { Observable, Subscription } from 'rxjs';
import database from '../../lib/database'; import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import { TSubscriptionModel, TUserModel } from '../../definitions'; import { IApplicationState, TSubscriptionModel, TUserModel } from '../../definitions';
import Avatar from './Avatar'; import Avatar from './Avatar';
import { IAvatar } from './interfaces'; import { IAvatar } from './interfaces';
class AvatarContainer extends React.Component<IAvatar, any> { class AvatarContainer extends React.Component<IAvatar, any> {
private mounted: boolean;
private subscription?: Subscription; private subscription?: Subscription;
static defaultProps = { static defaultProps = {
@ -21,16 +19,11 @@ class AvatarContainer extends React.Component<IAvatar, any> {
constructor(props: IAvatar) { constructor(props: IAvatar) {
super(props); super(props);
this.mounted = false;
this.state = { avatarETag: '' }; this.state = { avatarETag: '' };
this.init(); this.init();
} }
componentDidMount() { componentDidUpdate(prevProps: IAvatar) {
this.mounted = true;
}
componentDidUpdate(prevProps: any) {
const { text, type } = this.props; const { text, type } = this.props;
if (prevProps.text !== text || prevProps.type !== type) { if (prevProps.text !== text || prevProps.type !== type) {
this.init(); this.init();
@ -88,12 +81,7 @@ class AvatarContainer extends React.Component<IAvatar, any> {
const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>; const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
this.subscription = observable.subscribe(r => { this.subscription = observable.subscribe(r => {
const { avatarETag } = r; const { avatarETag } = r;
if (this.mounted) {
this.setState({ avatarETag }); this.setState({ avatarETag });
} else {
// @ts-ignore
this.state.avatarETag = avatarETag;
}
}); });
} }
}; };
@ -105,12 +93,12 @@ class AvatarContainer extends React.Component<IAvatar, any> {
} }
} }
const mapStateToProps = (state: any) => ({ const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state), user: getUserSelector(state),
server: state.share.server.server || state.server.server, server: state.share.server.server || state.server.server,
serverVersion: state.share.server.version || state.server.version, serverVersion: state.share.server.version || state.server.version,
blockUnauthenticatedAccess: blockUnauthenticatedAccess:
state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess ?? (state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
true true
}); });

View File

@ -16,12 +16,11 @@ export interface IAvatar {
id?: string; id?: string;
token?: string; token?: string;
}; };
theme?: string;
onPress?: () => void; onPress?: () => void;
getCustomEmoji?: TGetCustomEmoji; getCustomEmoji?: TGetCustomEmoji;
avatarETag?: string; avatarETag?: string;
isStatic?: boolean | string; isStatic?: boolean | string;
rid?: string; rid?: string;
blockUnauthenticatedAccess?: boolean; blockUnauthenticatedAccess?: boolean;
serverVersion: string; serverVersion: string | null;
} }

View File

@ -285,7 +285,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
toValue: height, toValue: height,
duration: 300, duration: 300,
easing: Easing.inOut(Easing.quad), easing: Easing.inOut(Easing.quad),
useNativeDriver: true useNativeDriver: false
}).start(); }).start();
}; };

View File

@ -1,5 +1,6 @@
import React, { forwardRef, useImperativeHandle } from 'react'; import React, { forwardRef, useImperativeHandle } from 'react';
import { Alert, Clipboard, Share } from 'react-native'; import { Alert, Share } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import moment from 'moment'; import moment from 'moment';

View File

@ -74,7 +74,7 @@ interface IItem {
interface IModalContent { interface IModalContent {
message?: TMessageModel; message?: TMessageModel;
onClose: Function; onClose: () => void;
theme: string; theme: string;
} }

View File

@ -47,11 +47,11 @@ const styles = StyleSheet.create({
interface ISearchBox extends TextInputProps { interface ISearchBox extends TextInputProps {
value?: string; value?: string;
hasCancel?: boolean; hasCancel?: boolean;
onCancelPress?: Function; onCancelPress?: () => void;
inputRef?: React.Ref<RNTextInput>; inputRef?: React.Ref<RNTextInput>;
} }
const CancelButton = ({ onCancelPress }: { onCancelPress?: Function }) => { const CancelButton = ({ onCancelPress }: { onCancelPress?: () => void }) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
<Touchable onPress={onCancelPress} style={styles.cancel}> <Touchable onPress={onCancelPress} style={styles.cancel}>
@ -84,7 +84,7 @@ const SearchBox = ({ hasCancel, onCancelPress, inputRef, ...props }: ISearchBox)
{...props} {...props}
/> />
</View> </View>
{hasCancel ? <CancelButton onCancelPress={onCancelPress} /> : null} {hasCancel && onCancelPress ? <CancelButton onCancelPress={onCancelPress} /> : null}
</View> </View>
); );
}; };

View File

@ -4,8 +4,10 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import Button from '../Button'; import Button from '../Button';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { IActions } from './interfaces'; import { IActions } from './interfaces';
import { useTheme } from '../../theme';
export const Actions = ({ blockId, appId, elements, parser, theme }: IActions) => { export const Actions = ({ blockId, appId, elements, parser }: IActions) => {
const { theme } = useTheme();
const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5); const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5);
const renderedElements = showMoreVisible ? elements?.slice(0, 5) : elements; const renderedElements = showMoreVisible ? elements?.slice(0, 5) : elements;

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker'; import DateTimePicker, { Event } from '@react-native-community/datetimepicker';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import moment from 'moment'; import moment from 'moment';
@ -11,6 +11,7 @@ import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { isAndroid } from '../../utils/deviceInfo'; import { isAndroid } from '../../utils/deviceInfo';
import { useTheme } from '../../theme';
import ActivityIndicator from '../ActivityIndicator'; import ActivityIndicator from '../ActivityIndicator';
import { IDatePicker } from './interfaces'; import { IDatePicker } from './interfaces';
@ -36,14 +37,17 @@ const styles = StyleSheet.create({
} }
}); });
export const DatePicker = ({ element, language, action, context, theme, loading, value, error }: IDatePicker) => { export const DatePicker = ({ element, language, action, context, loading, value, error }: IDatePicker) => {
const { theme } = useTheme();
const [show, onShow] = useState(false); const [show, onShow] = useState(false);
const initial_date = element?.initial_date; const initial_date = element?.initial_date;
const placeholder = element?.placeholder; const placeholder = element?.placeholder;
const [currentDate, onChangeDate] = useState(new Date(initial_date || value)); const [currentDate, onChangeDate] = useState(new Date(initial_date || value));
const onChange = ({ nativeEvent: { timestamp } }: any, date: any) => { // timestamp as number exists in Event
// @ts-ignore
const onChange = ({ nativeEvent: { timestamp } }: Event, date?: Date) => {
const newDate = date || new Date(timestamp); const newDate = date || new Date(timestamp);
onChangeDate(newDate); onChangeDate(newDate);
action({ value: moment(newDate).format('YYYY-MM-DD') }); action({ value: moment(newDate).format('YYYY-MM-DD') });
@ -52,7 +56,9 @@ export const DatePicker = ({ element, language, action, context, theme, loading,
} }
}; };
let button = <Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} theme={theme} />; let button = placeholder ? (
<Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} theme={theme} />
) : null;
if (context === BLOCK_CONTEXT.FORM) { if (context === BLOCK_CONTEXT.FORM) {
button = ( button = (

View File

@ -6,7 +6,7 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import ImageContainer from '../message/Image'; import ImageContainer from '../message/Image';
import Navigation from '../../lib/Navigation'; import Navigation from '../../lib/Navigation';
import { IThumb, IImage, IElement } from './interfaces'; import { IThumb, IImage, IElement } from './interfaces';
import { TThemeMode } from '../../definitions/ITheme'; import { IAttachment } from '../../definitions';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
image: { image: {
@ -27,23 +27,22 @@ export const Thumb = ({ element, size = 88 }: IThumb) => (
<FastImage style={[{ width: size, height: size }, styles.image]} source={{ uri: element?.imageUrl }} /> <FastImage style={[{ width: size, height: size }, styles.image]} source={{ uri: element?.imageUrl }} />
); );
export const Media = ({ element, theme }: IImage) => { export const Media = ({ element }: IImage) => {
const showAttachment = (attachment: any) => Navigation.navigate('AttachmentView', { attachment }); const showAttachment = (attachment: IAttachment) => Navigation.navigate('AttachmentView', { attachment });
const imageUrl = element?.imageUrl ?? ''; const imageUrl = element?.imageUrl ?? '';
// @ts-ignore
// TODO: delete ts-ignore after refactor Markdown and ImageContainer return <ImageContainer file={{ image_url: imageUrl }} imageUrl={imageUrl} showAttachment={showAttachment} />;
return <ImageContainer file={{ image_url: imageUrl }} imageUrl={imageUrl} showAttachment={showAttachment} theme={theme} />;
}; };
const genericImage = (theme: TThemeMode, element: IElement, context?: number) => { const genericImage = (element: IElement, context?: number) => {
switch (context) { switch (context) {
case BLOCK_CONTEXT.SECTION: case BLOCK_CONTEXT.SECTION:
return <Thumb element={element} />; return <Thumb element={element} />;
case BLOCK_CONTEXT.CONTEXT: case BLOCK_CONTEXT.CONTEXT:
return <ThumbContext element={element} />; return <ThumbContext element={element} />;
default: default:
return <Media element={element} theme={theme} />; return <Media element={element} />;
} }
}; };
export const Image = ({ element, context, theme }: IImage) => genericImage(theme, element, context); export const Image = ({ element, context }: IImage) => genericImage(element, context);

View File

@ -7,26 +7,23 @@ import { themes } from '../../../constants/colors';
import { textParser } from '../utils'; import { textParser } from '../utils';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
import styles from './styles'; import styles from './styles';
import { IItemData } from '.';
interface IChip { interface IChip {
item: { item: IItemData;
value: string; onSelect: (item: IItemData) => void;
imageUrl: string;
text: string;
};
onSelect: Function;
style?: object; style?: object;
theme: string; theme: string;
} }
interface IChips { interface IChips {
items: []; items: IItemData[];
onSelect: Function; onSelect: (item: IItemData) => void;
style?: object; style?: object;
theme: string; theme: string;
} }
const keyExtractor = (item: any) => item.value.toString(); const keyExtractor = (item: IItemData) => item.value.toString();
const Chip = ({ item, onSelect, style, theme }: IChip) => ( const Chip = ({ item, onSelect, style, theme }: IChip) => (
<Touchable <Touchable

View File

@ -9,10 +9,10 @@ import styles from './styles';
interface IInput { interface IInput {
children?: JSX.Element; children?: JSX.Element;
onPress: Function; onPress: () => void;
theme: string; theme: string;
inputStyle?: object; inputStyle?: object;
disabled?: boolean | object; disabled?: boolean | null;
placeholder?: string; placeholder?: string;
loading?: boolean; loading?: boolean;
innerInputStyle?: object; innerInputStyle?: object;

View File

@ -8,34 +8,31 @@ import * as List from '../../List';
import { textParser } from '../utils'; import { textParser } from '../utils';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import styles from './styles'; import styles from './styles';
import { IItemData } from '.';
interface IItem { interface IItem {
item: { item: IItemData;
value: { name: string }; selected?: string;
text: { text: string };
imageUrl: string;
};
selected: any;
onSelect: Function; onSelect: Function;
theme: string; theme: string;
} }
interface IItems { interface IItems {
items: []; items: IItemData[];
selected: []; selected: string[];
onSelect: Function; onSelect: Function;
theme: string; theme: string;
} }
const keyExtractor = (item: any) => item.value.toString(); const keyExtractor = (item: IItemData) => item.value.toString();
// RectButton doesn't work on modal (Android) // RectButton doesn't work on modal (Android)
const Item = ({ item, selected, onSelect, theme }: IItem) => { const Item = ({ item, selected, onSelect, theme }: IItem) => {
const itemName = item.value.name || item.text.text.toLowerCase(); const itemName = item.value || item.text.text.toLowerCase();
return ( return (
<Touchable <Touchable
testID={`multi-select-item-${itemName}`} testID={`multi-select-item-${itemName}`}
key={item} key={itemName}
onPress={() => onSelect(item)} onPress={() => onSelect(item)}
style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}> style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}>
<> <>

View File

@ -1,5 +1,15 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Animated, Easing, KeyboardAvoidingView, Modal, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'; import {
Animated,
Easing,
KeyboardAvoidingView,
Modal,
StyleSheet,
Text,
TouchableWithoutFeedback,
View,
TextStyle
} from 'react-native';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import Button from '../../Button'; import Button from '../../Button';
@ -8,26 +18,31 @@ import { textParser } from '../utils';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import { isIOS } from '../../../utils/deviceInfo'; import { isIOS } from '../../../utils/deviceInfo';
import { useTheme } from '../../../theme';
import { BlockContext, IText } from '../interfaces';
import Chips from './Chips'; import Chips from './Chips';
import Items from './Items'; import Items from './Items';
import Input from './Input'; import Input from './Input';
import styles from './styles'; import styles from './styles';
export interface IItemData {
value: string;
text: { text: string };
imageUrl?: string;
}
interface IMultiSelect { interface IMultiSelect {
options: any[]; options?: IItemData[];
onChange: Function; onChange: Function;
placeholder: { placeholder?: IText;
text: string; context?: BlockContext;
};
context?: number;
loading?: boolean; loading?: boolean;
multiselect?: boolean; multiselect?: boolean;
onSearch?: () => void; onSearch?: () => void;
onClose?: () => void; onClose?: () => void;
inputStyle?: object; inputStyle?: TextStyle;
value?: any[]; value?: any[];
disabled?: boolean | object; disabled?: boolean | null;
theme: string;
innerInputStyle?: object; innerInputStyle?: object;
} }
@ -54,9 +69,9 @@ export const MultiSelect = React.memo(
onClose = () => {}, onClose = () => {},
disabled, disabled,
inputStyle, inputStyle,
theme,
innerInputStyle innerInputStyle
}: IMultiSelect) => { }: IMultiSelect) => {
const { theme } = useTheme();
const [selected, select] = useState<any>(Array.isArray(values) ? values : []); const [selected, select] = useState<any>(Array.isArray(values) ? values : []);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [search, onSearchChange] = useState(''); const [search, onSearchChange] = useState('');
@ -95,7 +110,7 @@ export const MultiSelect = React.memo(
}).start(() => setShowContent(false)); }).start(() => setShowContent(false));
}; };
const onSelect = (item: any) => { const onSelect = (item: IItemData) => {
const { const {
value, value,
text: { text } text: { text }

View File

@ -6,6 +6,7 @@ import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import ActivityIndicator from '../ActivityIndicator'; import ActivityIndicator from '../ActivityIndicator';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { useTheme } from '../../theme';
import { BUTTON_HIT_SLOP } from '../message/utils'; import { BUTTON_HIT_SLOP } from '../message/utils';
import * as List from '../List'; import * as List from '../List';
import { IOption, IOptions, IOverflow } from './interfaces'; import { IOption, IOptions, IOverflow } from './interfaces';
@ -43,9 +44,10 @@ const Options = ({ options, onOptionPress, parser, theme }: IOptions) => (
/> />
); );
const touchable: { [key: string]: any } = {}; const touchable: { [key: string]: Touchable | null } = {};
export const Overflow = ({ element, loading, action, parser, theme }: IOverflow) => { export const Overflow = ({ element, loading, action, parser }: IOverflow) => {
const { theme } = useTheme();
const options = element?.options || []; const options = element?.options || [];
const blockId = element?.blockId || ''; const blockId = element?.blockId || '';
const [show, onShow] = useState(false); const [show, onShow] = useState(false);
@ -58,7 +60,7 @@ export const Overflow = ({ element, loading, action, parser, theme }: IOverflow)
return ( return (
<> <>
<Touchable <Touchable
ref={(ref: any) => (touchable[blockId] = ref)} ref={ref => (touchable[blockId] = ref)}
background={Touchable.Ripple(themes[theme].bannerBackground)} background={Touchable.Ripple(themes[theme].bannerBackground)}
onPress={() => onShow(!show)} onPress={() => onShow(!show)}
hitSlop={BUTTON_HIT_SLOP} hitSlop={BUTTON_HIT_SLOP}
@ -71,6 +73,7 @@ export const Overflow = ({ element, loading, action, parser, theme }: IOverflow)
</Touchable> </Touchable>
<Popover <Popover
isVisible={show} isVisible={show}
// fromView exists in Popover Component
/* @ts-ignore*/ /* @ts-ignore*/
fromView={touchable[blockId]} fromView={touchable[blockId]}
onRequestClose={() => onShow(false)}> onRequestClose={() => onShow(false)}>

View File

@ -4,6 +4,7 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { IAccessoryComponent, IFields, ISection } from './interfaces'; import { IAccessoryComponent, IFields, ISection } from './interfaces';
import { useTheme } from '../../theme';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
content: { content: {
@ -37,10 +38,14 @@ const Fields = ({ fields, parser, theme }: IFields) => (
const accessoriesRight = ['image', 'overflow']; const accessoriesRight = ['image', 'overflow'];
export const Section = ({ blockId, appId, text, fields, accessory, parser, theme }: ISection) => ( export const Section = ({ blockId, appId, text, fields, accessory, parser }: ISection) => {
const { theme } = useTheme();
return (
<View style={[styles.content, accessory && accessoriesRight.includes(accessory.type) ? styles.row : styles.column]}> <View style={[styles.content, accessory && accessoriesRight.includes(accessory.type) ? styles.row : styles.column]}>
{text ? <View style={styles.text}>{parser.text(text)}</View> : null} {text ? <View style={styles.text}>{parser.text(text)}</View> : null}
{fields ? <Fields fields={fields} theme={theme} parser={parser} /> : null} {fields ? <Fields fields={fields} theme={theme} parser={parser} /> : null}
{accessory ? <Accessory element={{ blockId, appId, ...accessory }} parser={parser} /> : null} {accessory ? <Accessory element={{ blockId, appId, ...accessory }} parser={parser} /> : null}
</View> </View>
); );
};

View File

@ -8,6 +8,8 @@ import { CustomIcon } from '../../lib/Icons';
import { textParser } from './utils'; import { textParser } from './utils';
import { isAndroid, isIOS } from '../../utils/deviceInfo'; import { isAndroid, isIOS } from '../../utils/deviceInfo';
import ActivityIndicator from '../ActivityIndicator'; import ActivityIndicator from '../ActivityIndicator';
import { useTheme } from '../../theme';
import { IText, Option } from './interfaces';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
iosPadding: { iosPadding: {
@ -34,19 +36,16 @@ const styles = StyleSheet.create({
}); });
interface ISelect { interface ISelect {
options: { options?: Option[];
text: string; placeholder?: IText;
value: string;
}[];
placeholder: string;
onChange: Function; onChange: Function;
loading: boolean; loading: boolean;
disabled: boolean; disabled?: boolean;
value: []; value: [];
theme: string;
} }
export const Select = ({ options = [], placeholder, onChange, loading, disabled, value: initialValue, theme }: ISelect) => { export const Select = ({ options = [], placeholder, onChange, loading, disabled, value: initialValue }: ISelect) => {
const { theme } = useTheme();
const [selected, setSelected] = useState(!Array.isArray(initialValue) && initialValue); const [selected, setSelected] = useState(!Array.isArray(initialValue) && initialValue);
const items = options.map(option => ({ label: textParser([option.text]), value: option.value })); const items = options.map(option => ({ label: textParser([option.text]), value: option.value }));
const pickerStyle = { const pickerStyle = {
@ -80,6 +79,7 @@ export const Select = ({ options = [], placeholder, onChange, loading, disabled,
}} }}
Icon={Icon} Icon={Icon}
textInputProps={{ textInputProps={{
// style property was Omitted in lib, but can be used normally
// @ts-ignore // @ts-ignore
style: { ...styles.pickerText, color: selected ? themes[theme].titleText : themes[theme].auxiliaryText } style: { ...styles.pickerText, color: selected ? themes[theme].titleText : themes[theme].auxiliaryText }
}} }}

View File

@ -20,7 +20,7 @@ import { Input } from './Input';
import { DatePicker } from './DatePicker'; import { DatePicker } from './DatePicker';
import { Overflow } from './Overflow'; import { Overflow } from './Overflow';
import { ThemeContext } from '../../theme'; import { ThemeContext } from '../../theme';
import { BlockContext, IButton, IInputIndex, IParser, IText } from './interfaces'; import { BlockContext, IActions, IButton, IElement, IInputIndex, IParser, ISection, IText } from './interfaces';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
input: { input: {
@ -78,35 +78,28 @@ class MessageParser extends UiKitParserMessage {
} }
divider() { divider() {
const { theme } = useContext(ThemeContext); return <Divider />;
// @ts-ignore
return <Divider theme={theme} />;
} }
section(args: any) { section(args: ISection) {
const { theme } = useContext(ThemeContext); return <Section {...args} parser={this.current} />;
return <Section {...args} theme={theme} parser={this} />;
} }
actions(args: any) { actions(args: IActions) {
const { theme } = useContext(ThemeContext); return <Actions {...args} parser={this.current} />;
return <Actions {...args} theme={theme} parser={this} />;
} }
overflow(element: any, context: any) { overflow(element: IElement, context: BlockContext) {
const [{ loading }, action]: any = useBlockContext(element, context); const [{ loading }, action] = useBlockContext(element, context);
const { theme }: any = useContext(ThemeContext); return <Overflow element={element} context={context} loading={loading} action={action} parser={this.current} />;
return <Overflow element={element} context={context} loading={loading} action={action} theme={theme} parser={this.current} />;
} }
datePicker(element: any, context: any) { datePicker(element: IElement, context: BlockContext) {
const [{ loading, value, error, language }, action]: any = useBlockContext(element, context); const [{ loading, value, error, language }, action] = useBlockContext(element, context);
const { theme }: any = useContext(ThemeContext);
return ( return (
<DatePicker <DatePicker
element={element} element={element}
language={language} language={language}
theme={theme}
value={value} value={value}
action={action} action={action}
context={context} context={context}
@ -116,9 +109,8 @@ class MessageParser extends UiKitParserMessage {
); );
} }
image(element: any, context: any) { image(element: IElement, context: BlockContext) {
const { theme }: any = useContext(ThemeContext); return <Image element={element} context={context} />;
return <Image element={element} theme={theme} context={context} />;
} }
context(args: any) { context(args: any) {
@ -126,24 +118,19 @@ class MessageParser extends UiKitParserMessage {
return <Context {...args} theme={theme} parser={this} />; return <Context {...args} theme={theme} parser={this} />;
} }
multiStaticSelect(element: any, context: any) { multiStaticSelect(element: IElement, context: BlockContext) {
const [{ loading, value }, action]: any = useBlockContext(element, context); const [{ loading, value }, action] = useBlockContext(element, context);
const { theme } = useContext(ThemeContext); return <MultiSelect {...element} value={value} onChange={action} context={context} loading={loading} multiselect />;
return (
<MultiSelect {...element} theme={theme} value={value} onChange={action} context={context} loading={loading} multiselect />
);
} }
staticSelect(element: any, context: any) { staticSelect(element: IElement, context: BlockContext) {
const [{ loading, value }, action]: any = useBlockContext(element, context); const [{ loading, value }, action] = useBlockContext(element, context);
const { theme } = useContext(ThemeContext); return <Select {...element} value={value} onChange={action} loading={loading} />;
return <Select {...element} theme={theme} value={value} onChange={action} loading={loading} />;
} }
selectInput(element: any, context: any) { selectInput(element: IElement, context: BlockContext) {
const [{ loading, value }, action]: any = useBlockContext(element, context); const [{ loading, value }, action] = useBlockContext(element, context);
const { theme } = useContext(ThemeContext); return <MultiSelect {...element} value={value} onChange={action} context={context} loading={loading} />;
return <MultiSelect {...element} theme={theme} value={value} onChange={action} context={context} loading={loading} />;
} }
} }
@ -160,8 +147,8 @@ class ModalParser extends UiKitParserModal {
} }
input({ element, blockId, appId, label, description, hint }: IInputIndex, context: number) { input({ element, blockId, appId, label, description, hint }: IInputIndex, context: number) {
const [{ error }]: any = useBlockContext({ ...element, appId, blockId }, context); const [{ error }] = useBlockContext({ ...element, appId, blockId }, context);
const { theme }: any = useContext(ThemeContext); const { theme } = useContext(ThemeContext);
return ( return (
<Input <Input
parser={this.current} parser={this.current}
@ -175,17 +162,15 @@ class ModalParser extends UiKitParserModal {
); );
} }
image(element: any, context: any) { image(element: IElement, context: BlockContext) {
const { theme }: any = useContext(ThemeContext); return <Image element={element} context={context} />;
return <Image element={element} theme={theme} context={context} />;
} }
plainInput(element: any, context: any) { plainInput(element: IElement, context: BlockContext) {
const [{ loading, value, error }, action]: any = useBlockContext(element, context); const [{ loading, value, error }, action] = useBlockContext(element, context);
const { theme } = useContext(ThemeContext); const { theme } = useContext(ThemeContext);
const { multiline, actionId, placeholder } = element; const { multiline, actionId, placeholder } = element;
return ( return (
// @ts-ignore
<TextInput <TextInput
key={actionId} key={actionId}
placeholder={plainText(placeholder)} placeholder={plainText(placeholder)}

View File

@ -1,5 +1,3 @@
import { TThemeMode } from '../../definitions/ITheme';
export enum ElementTypes { export enum ElementTypes {
IMAGE = 'image', IMAGE = 'image',
BUTTON = 'button', BUTTON = 'button',
@ -87,10 +85,11 @@ export interface IElement {
imageUrl?: string; imageUrl?: string;
appId?: string; appId?: string;
blockId?: string; blockId?: string;
multiline?: boolean;
} }
export interface IText { export interface IText {
type: ElementTypes; type?: ElementTypes;
text: string; text: string;
emoji?: boolean; emoji?: boolean;
} }
@ -98,12 +97,15 @@ export interface IText {
export interface Option { export interface Option {
text: IText; text: IText;
value: string; value: string;
imageUrl?: string;
} }
export interface IButton { export interface IButton {
type: ElementTypes; type: ElementTypes;
text: IText; text: IText;
actionId: string; actionId: string;
blockId: string;
appId: string;
value?: any; value?: any;
style?: any; style?: any;
} }
@ -177,7 +179,6 @@ export interface IParser {
} }
export interface IActions extends Block { export interface IActions extends Block {
parser?: IParser; parser?: IParser;
theme: TThemeMode;
} }
export interface IContext extends Block { export interface IContext extends Block {
@ -191,7 +192,6 @@ export interface IDatePicker extends Partial<Block> {
loading: boolean; loading: boolean;
value: string; value: string;
error: string; error: string;
theme: TThemeMode;
} }
export interface IInput extends Partial<Block> { export interface IInput extends Partial<Block> {
@ -199,7 +199,7 @@ export interface IInput extends Partial<Block> {
description: string; description: string;
error: string; error: string;
hint: string; hint: string;
theme: TThemeMode; theme: string;
} }
export interface IInputIndex { export interface IInputIndex {
@ -217,8 +217,7 @@ export interface IThumb {
} }
export interface IImage { export interface IImage {
element: IElement; element: IElement;
theme: TThemeMode; context?: BlockContext;
context?: number;
} }
// UiKit/Overflow // UiKit/Overflow
@ -226,14 +225,13 @@ export interface IOverflow extends Partial<Block> {
action: Function; action: Function;
loading: boolean; loading: boolean;
parser: IParser; parser: IParser;
theme: TThemeMode;
context: number; context: number;
} }
interface PropsOption { interface PropsOption {
onOptionPress: Function; onOptionPress: Function;
parser: IParser; parser: IParser;
theme: TThemeMode; theme: string;
} }
export interface IOptions extends PropsOption { export interface IOptions extends PropsOption {
options: Option[]; options: Option[];
@ -262,12 +260,11 @@ export interface ISection {
text?: IText; text?: IText;
accessory?: IAccessory; accessory?: IAccessory;
parser: IParser; parser: IParser;
theme: TThemeMode;
fields?: any[]; fields?: any[];
} }
export interface IFields { export interface IFields {
parser: IParser; parser: IParser;
theme: TThemeMode; theme: string;
fields: any[]; fields: any[];
} }

View File

@ -2,9 +2,9 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import { BlockContext } from './interfaces'; import { BlockContext, IText } from './interfaces';
export const textParser = ([{ text }]: any) => text; export const textParser = ([{ text }]: IText[]) => text;
export const defaultContext: any = { export const defaultContext: any = {
action: (...args: any) => console.log(args), action: (...args: any) => console.log(args),
@ -27,7 +27,14 @@ type TFunctionReturn = (value: any) => Promise<void>;
type TReturn = [TObjectReturn, TFunctionReturn]; type TReturn = [TObjectReturn, TFunctionReturn];
export const useBlockContext = ({ blockId, actionId, appId, initialValue }: any, context: BlockContext): TReturn => { interface IUseBlockContext {
blockId?: string;
actionId: string;
appId?: string;
initialValue?: string;
}
export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUseBlockContext, context: BlockContext): TReturn => {
const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext); const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext);
const { value = initialValue } = values[actionId] || {}; const { value = initialValue } = values[actionId] || {};
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { Clipboard, Text } from 'react-native'; import { Text } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';

View File

@ -1,6 +1,7 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Text, Clipboard } from 'react-native'; import { Text } from 'react-native';
import { Link as LinkProps } from '@rocket.chat/message-parser'; import { Link as LinkProps } from '@rocket.chat/message-parser';
import Clipboard from '@react-native-clipboard/clipboard';
import styles from '../styles'; import styles from '../styles';
import I18n from '../../../i18n'; import I18n from '../../../i18n';

View File

@ -13,6 +13,15 @@ import MessageContext from './Context';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { IAttachment } from '../../definitions'; import { IAttachment } from '../../definitions';
import CollapsibleQuote from './Components/CollapsibleQuote'; import CollapsibleQuote from './Components/CollapsibleQuote';
import openLink from '../../utils/openLink';
import { themes } from '../../constants/colors';
export type TElement = {
type: string;
msg?: string;
url?: string;
text: string;
};
const AttachedActions = ({ attachment }: IMessageAttachedActions) => { const AttachedActions = ({ attachment }: IMessageAttachedActions) => {
if (!attachment.actions) { if (!attachment.actions) {
@ -21,15 +30,26 @@ const AttachedActions = ({ attachment }: IMessageAttachedActions) => {
const { onAnswerButtonPress } = useContext(MessageContext); const { onAnswerButtonPress } = useContext(MessageContext);
const { theme } = useTheme(); const { theme } = useTheme();
const attachedButtons = attachment.actions.map((element: { type: string; msg: string; text: string }) => { const attachedButtons = attachment.actions.map((element: TElement) => {
if (element.type === 'button') { const onPress = () => {
return <Button theme={theme} onPress={() => onAnswerButtonPress(element.msg)} title={element.text} />; if (element.msg) {
onAnswerButtonPress(element.msg);
} }
if (element.url) {
openLink(element.url);
}
};
if (element.type === 'button') {
return <Button theme={theme} onPress={onPress} title={element.text} />;
}
return null; return null;
}); });
return ( return (
<> <>
<Text style={styles.text}>{attachment.text}</Text> <Text style={[styles.text, { color: themes[theme].bodyText }]}>{attachment.text}</Text>
{attachedButtons} {attachedButtons}
</> </>
); );
@ -54,7 +74,6 @@ const Attachments = React.memo(
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
style={style} style={style}
isReply={isReply} isReply={isReply}
theme={theme}
/> />
); );
} }

View File

@ -4,19 +4,6 @@ import React from 'react';
import MessageContext from '../../Context'; import MessageContext from '../../Context';
import CollapsibleQuote from '.'; import CollapsibleQuote from '.';
// For some reason a general mock didn't work, I have to do a search
jest.mock('react-native-mmkv-storage', () => ({
Loader: jest.fn().mockImplementation(() => ({
setProcessingMode: jest.fn().mockImplementation(() => ({
withEncryption: jest.fn().mockImplementation(() => ({
initialize: jest.fn()
}))
}))
})),
create: jest.fn(),
MODES: { MULTI_PROCESS: '' }
}));
const testAttachment = { const testAttachment = {
ts: '1970-01-01T00:00:00.000Z', ts: '1970-01-01T00:00:00.000Z',
title: 'Engineering (9 today)', title: 'Engineering (9 today)',

View File

@ -12,6 +12,7 @@ import { formatAttachmentUrl } from '../../lib/utils';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import MessageContext from './Context'; import MessageContext from './Context';
import { TGetCustomEmoji } from '../../definitions/IEmoji'; import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { useTheme } from '../../theme';
import { IAttachment } from '../../definitions'; import { IAttachment } from '../../definitions';
type TMessageButton = { type TMessageButton = {
@ -32,8 +33,7 @@ interface IMessageImage {
showAttachment?: Function; showAttachment?: Function;
style?: StyleProp<TextStyle>[]; style?: StyleProp<TextStyle>[];
isReply?: boolean; isReply?: boolean;
theme: string; getCustomEmoji?: TGetCustomEmoji;
getCustomEmoji: TGetCustomEmoji;
} }
const ImageProgress = createImageProgress(FastImage); const ImageProgress = createImageProgress(FastImage);
@ -61,7 +61,8 @@ export const MessageImage = React.memo(({ img, theme }: TMessageImage) => (
)); ));
const ImageContainer = React.memo( const ImageContainer = React.memo(
({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageImage) => { ({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply }: IMessageImage) => {
const { theme } = useTheme();
const { baseUrl, user } = useContext(MessageContext); const { baseUrl, user } = useContext(MessageContext);
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl); const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
if (!img) { if (!img) {
@ -100,7 +101,7 @@ const ImageContainer = React.memo(
</Button> </Button>
); );
}, },
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme (prevProps, nextProps) => dequal(prevProps.file, nextProps.file)
); );
ImageContainer.displayName = 'MessageImageContainer'; ImageContainer.displayName = 'MessageImageContainer';

View File

@ -4,13 +4,13 @@ import Avatar from '../Avatar';
import styles from './styles'; import styles from './styles';
import MessageContext from './Context'; import MessageContext from './Context';
import { IMessageAvatar } from './interfaces'; import { IMessageAvatar } from './interfaces';
import { SubscriptionType } from '../../definitions';
const MessageAvatar = React.memo( const MessageAvatar = React.memo(({ isHeader, avatar, author, small, navToRoomInfo, emoji, getCustomEmoji }: IMessageAvatar) => {
({ isHeader, avatar, author, small, navToRoomInfo, emoji, getCustomEmoji, theme }: IMessageAvatar) => {
const { user } = useContext(MessageContext); const { user } = useContext(MessageContext);
if (isHeader && author) { if (isHeader && author) {
const navParam = { const navParam = {
t: 'd', t: SubscriptionType.DIRECT,
rid: author._id rid: author._id
}; };
return ( return (
@ -23,13 +23,11 @@ const MessageAvatar = React.memo(
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
avatar={avatar} avatar={avatar}
emoji={emoji} emoji={emoji}
theme={theme}
/> />
); );
} }
return null; return null;
} });
);
MessageAvatar.displayName = 'MessageAvatar'; MessageAvatar.displayName = 'MessageAvatar';

View File

@ -1,5 +1,6 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Clipboard, StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import FastImage from '@rocket.chat/react-native-fast-image'; import FastImage from '@rocket.chat/react-native-fast-image';
import { dequal } from 'dequal'; import { dequal } from 'dequal';

View File

@ -31,7 +31,6 @@ export interface IMessageAvatar {
small?: boolean; small?: boolean;
navToRoomInfo: Function; navToRoomInfo: Function;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
theme: string;
} }
export interface IMessageBlocks { export interface IMessageBlocks {

View File

@ -2,7 +2,7 @@ import { IUser } from './IUser';
export interface IAttachment { export interface IAttachment {
ts?: string | Date; ts?: string | Date;
title: string; title?: string;
type?: string; type?: string;
description?: string; description?: string;
title_link?: string; title_link?: string;

View File

@ -3,7 +3,6 @@ declare module 'commonmark';
declare module 'commonmark-react-renderer'; declare module 'commonmark-react-renderer';
declare module 'remove-markdown'; declare module 'remove-markdown';
declare module 'react-native-image-progress'; declare module 'react-native-image-progress';
declare module 'react-native-platform-touchable';
declare module 'react-native-ui-lib/keyboard'; declare module 'react-native-ui-lib/keyboard';
declare module '@rocket.chat/ui-kit'; declare module '@rocket.chat/ui-kit';
declare module '@rocket.chat/sdk'; declare module '@rocket.chat/sdk';

View File

@ -32,9 +32,9 @@ export function subscribeUsersPresence(this: IRocketChat) {
sdk.subscribe('stream-notify-logged', 'Users:NameChanged'); sdk.subscribe('stream-notify-logged', 'Users:NameChanged');
} }
let ids: string[] = []; let usersBatch: string[] = [];
export default async function getUsersPresence() { export default async function getUsersPresence(usersParams: string[]) {
const serverVersion = reduxStore.getState().server.version as string; const serverVersion = reduxStore.getState().server.version as string;
const { user: loggedUser } = reduxStore.getState().login; const { user: loggedUser } = reduxStore.getState().login;
@ -45,11 +45,11 @@ export default async function getUsersPresence() {
// if server is greather than or equal 3.0.0 // if server is greather than or equal 3.0.0
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.0.0')) { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.0.0')) {
// if not have any id // if not have any id
if (!ids.length) { if (!usersParams.length) {
return; return;
} }
// Request userPresence on demand // Request userPresence on demand
params = { ids: ids.join(',') }; params = { ids: usersParams.join(',') };
} }
try { try {
@ -57,13 +57,13 @@ export default async function getUsersPresence() {
const result = (await sdk.get('users.presence' as any, params as any)) as any; const result = (await sdk.get('users.presence' as any, params as any)) as any;
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) {
sdk.subscribeRaw('stream-user-presence', ['', { added: ids }]); sdk.subscribeRaw('stream-user-presence', ['', { added: usersParams }]);
} }
if (result.success) { if (result.success) {
const { users } = result; const { users } = result;
const activeUsers = ids.reduce((ret: IActiveUsers, id) => { const activeUsers = usersParams.reduce((ret: IActiveUsers, id) => {
const user = users.find((u: IUser) => u._id === id) ?? { _id: id, status: 'offline' }; const user = users.find((u: IUser) => u._id === id) ?? { _id: id, status: 'offline' };
const { _id, status, statusText } = user; const { _id, status, statusText } = user;
@ -77,7 +77,6 @@ export default async function getUsersPresence() {
InteractionManager.runAfterInteractions(() => { InteractionManager.runAfterInteractions(() => {
reduxStore.dispatch(setActiveUsers(activeUsers)); reduxStore.dispatch(setActiveUsers(activeUsers));
}); });
ids = [];
const db = database.active; const db = database.active;
const userCollection = db.get('users'); const userCollection = db.get('users');
@ -110,12 +109,13 @@ let usersTimer: ReturnType<typeof setTimeout> | null = null;
export function getUserPresence(uid: string) { export function getUserPresence(uid: string) {
if (!usersTimer) { if (!usersTimer) {
usersTimer = setTimeout(() => { usersTimer = setTimeout(() => {
getUsersPresence(); getUsersPresence(usersBatch);
usersBatch = [];
usersTimer = null; usersTimer = null;
}, 2000); }, 2000);
} }
if (uid) { if (uid) {
ids.push(uid); usersBatch.push(uid);
} }
} }

View File

@ -16,7 +16,7 @@ import { RootEnum } from '../definitions';
import appConfig from '../../app.json'; import appConfig from '../../app.json';
export const initLocalSettings = function* initLocalSettings() { export const initLocalSettings = function* initLocalSettings() {
const sortPreferences = yield RocketChat.getSortPreferences(); const sortPreferences = RocketChat.getSortPreferences();
yield put(setAllPreferences(sortPreferences)); yield put(setAllPreferences(sortPreferences));
}; };
@ -32,8 +32,8 @@ const restore = function* restore() {
const serversCollection = serversDB.get('servers'); const serversCollection = serversDB.get('servers');
const servers = yield serversCollection.query().fetch(); const servers = yield serversCollection.query().fetch();
const isBiometryEnabled = servers.some(server => !!server.biometry); const isBiometryEnabled = servers.some(server => !!server.biometry);
yield UserPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, isBiometryEnabled); UserPreferences.setBool(BIOMETRY_ENABLED_KEY, isBiometryEnabled);
yield UserPreferences.setBoolAsync(BIOMETRY_MIGRATION_KEY, true); UserPreferences.setBool(BIOMETRY_MIGRATION_KEY, true);
} }
const { server } = appConfig; const { server } = appConfig;

View File

@ -55,7 +55,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
const serverHistoryRecord = serversHistory[0]; const serverHistoryRecord = serversHistory[0];
// this is updating on every login just to save `updated_at` // this is updating on every login just to save `updated_at`
// keeping this server as the most recent on autocomplete order // keeping this server as the most recent on autocomplete order
await serverHistoryRecord.update(s => { await serverHistoryRecord.update((s) => {
s.username = result.username; s.username = result.username;
}); });
} }
@ -151,12 +151,12 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield serversDB.action(async () => { yield serversDB.action(async () => {
try { try {
const userRecord = await usersCollection.find(user.id); const userRecord = await usersCollection.find(user.id);
await userRecord.update(record => { await userRecord.update((record) => {
record._raw = sanitizedRaw({ id: user.id, ...record._raw }, usersCollection.schema); record._raw = sanitizedRaw({ id: user.id, ...record._raw }, usersCollection.schema);
Object.assign(record, u); Object.assign(record, u);
}); });
} catch (e) { } catch (e) {
await usersCollection.create(record => { await usersCollection.create((record) => {
record._raw = sanitizedRaw({ id: user.id }, usersCollection.schema); record._raw = sanitizedRaw({ id: user.id }, usersCollection.schema);
Object.assign(record, u); Object.assign(record, u);
}); });

View File

@ -84,7 +84,9 @@ class AttachmentView extends React.Component<IAttachmentViewProps, IAttachmentVi
const attachment = route.params?.attachment; const attachment = route.params?.attachment;
let { title } = attachment; let { title } = attachment;
try { try {
if (title) {
title = decodeURI(title); title = decodeURI(title);
}
} catch { } catch {
// Do nothing // Do nothing
} }

View File

@ -48,14 +48,13 @@ const SelectChannel = ({
<> <>
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Parent_channel_or_group')}</Text> <Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Parent_channel_or_group')}</Text>
<MultiSelect <MultiSelect
theme={theme}
inputStyle={styles.inputStyle} inputStyle={styles.inputStyle}
onChange={onChannelSelect} onChange={onChannelSelect}
onSearch={getChannels} onSearch={getChannels}
value={initial && [initial]} value={initial && [initial]}
disabled={initial} disabled={!!initial}
options={channels.map(channel => ({ options={channels.map(channel => ({
value: channel, value: channel.name || channel.fname,
text: { text: RocketChat.getRoomTitle(channel) }, text: { text: RocketChat.getRoomTitle(channel) },
imageUrl: getAvatar(channel) imageUrl: getAvatar(channel)
}))} }))}

View File

@ -77,7 +77,6 @@ const SelectUsers = ({
<> <>
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Invite_users')}</Text> <Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Invite_users')}</Text>
<MultiSelect <MultiSelect
theme={theme}
inputStyle={styles.inputStyle} inputStyle={styles.inputStyle}
onSearch={getUsers} onSearch={getUsers}
onChange={onUserSelect} onChange={onUserSelect}

View File

@ -73,7 +73,7 @@ const Item = ({ item, onPress }: IItem): JSX.Element => {
testID={`discussions-view-${item.msg}`} testID={`discussions-view-${item.msg}`}
style={{ backgroundColor: themes[theme].backgroundColor }}> style={{ backgroundColor: themes[theme].backgroundColor }}>
<View style={styles.container}> <View style={styles.container}>
<Avatar style={styles.avatar} text={item?.u?.username} size={36} borderRadius={4} theme={theme} /> <Avatar style={styles.avatar} text={item?.u?.username} size={36} borderRadius={4} />
<View style={styles.contentContainer}> <View style={styles.contentContainer}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}> <Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { Clipboard, ScrollView, StyleSheet, Text, View } from 'react-native'; import { ScrollView, StyleSheet, Text, View } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { encryptionSetBanner } from '../actions/encryption'; import { encryptionSetBanner } from '../actions/encryption';

View File

@ -275,7 +275,6 @@ const LivechatEditView = ({
value={tagParamSelected} value={tagParamSelected}
context={BLOCK_CONTEXT.FORM} context={BLOCK_CONTEXT.FORM}
multiselect multiselect
theme={theme}
disabled={!permissions[1]} disabled={!permissions[1]}
inputStyle={styles.multiSelect} inputStyle={styles.multiSelect}
/> />

View File

@ -464,7 +464,6 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
renderSystemMessages = () => { renderSystemMessages = () => {
const { systemMessages, enableSysMes } = this.state; const { systemMessages, enableSysMes } = this.state;
const { theme } = this.props;
if (!enableSysMes) { if (!enableSysMes) {
return null; return null;
@ -481,7 +480,6 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
value={systemMessages as string[]} value={systemMessages as string[]}
context={BLOCK_CONTEXT.FORM} context={BLOCK_CONTEXT.FORM}
multiselect multiselect
theme={theme}
/> />
); );
}; };

View File

@ -2,7 +2,8 @@ import CookieManager from '@react-native-cookies/cookies';
import { StackNavigationOptions } from '@react-navigation/stack'; import { StackNavigationOptions } from '@react-navigation/stack';
import FastImage from '@rocket.chat/react-native-fast-image'; import FastImage from '@rocket.chat/react-native-fast-image';
import React from 'react'; import React from 'react';
import { Clipboard, Linking, Share } from 'react-native'; import { Linking, Share } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { appStart } from '../../actions/app'; import { appStart } from '../../actions/app';

View File

@ -166,7 +166,11 @@ class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
dispatch(setUser({ status: item.id })); dispatch(setUser({ status: item.id }));
} }
} catch (e: any) { } catch (e: any) {
showErrorAlert(I18n.t(e.data.errorType)); const messageError =
e.data && e.data.error.includes('[error-too-many-requests]')
? I18n.t('error-too-many-requests', { seconds: e.data.error.replace(/\D/g, '') })
: e.data.errorType;
showErrorAlert(messageError);
logEvent(events.SET_STATUS_FAIL); logEvent(events.SET_STATUS_FAIL);
log(e); log(e);
} }

View File

@ -65,7 +65,7 @@ interface IItem {
toggleFollowThread: (isFollowing: boolean, id: string) => void; toggleFollowThread: (isFollowing: boolean, id: string) => void;
} }
const Item = ({ item, useRealName, user, badgeColor, onPress, toggleFollowThread }: IItem) => { const Item = ({ item, useRealName, user, badgeColor, onPress, toggleFollowThread }: IItem): React.ReactElement => {
const { theme } = useTheme(); const { theme } = useTheme();
const username = (useRealName && item?.u?.name) || item?.u?.username; const username = (useRealName && item?.u?.name) || item?.u?.username;
let time; let time;
@ -79,7 +79,7 @@ const Item = ({ item, useRealName, user, badgeColor, onPress, toggleFollowThread
testID={`thread-messages-view-${item.msg}`} testID={`thread-messages-view-${item.msg}`}
style={{ backgroundColor: themes[theme].backgroundColor }}> style={{ backgroundColor: themes[theme].backgroundColor }}>
<View style={styles.container}> <View style={styles.container}>
<Avatar style={styles.avatar} text={item?.u?.username} size={36} borderRadius={4} theme={theme} /> <Avatar style={styles.avatar} text={item?.u?.username} size={36} borderRadius={4} />
<View style={styles.contentContainer}> <View style={styles.contentContainer}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}> <Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>

View File

@ -506,6 +506,8 @@ PODS:
- React-Core - React-Core
- RNCAsyncStorage (1.12.1): - RNCAsyncStorage (1.12.1):
- React-Core - React-Core
- RNCClipboard (1.8.5):
- React-Core
- RNCMaskedView (0.1.11): - RNCMaskedView (0.1.11):
- React - React
- RNConfigReader (1.0.0): - RNConfigReader (1.0.0):
@ -693,6 +695,7 @@ DEPENDENCIES:
- rn-fetch-blob (from `../node_modules/rn-fetch-blob`) - rn-fetch-blob (from `../node_modules/rn-fetch-blob`)
- RNBootSplash (from `../node_modules/react-native-bootsplash`) - RNBootSplash (from `../node_modules/react-native-bootsplash`)
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)" - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- "RNCMaskedView (from `../node_modules/@react-native-community/masked-view`)" - "RNCMaskedView (from `../node_modules/@react-native-community/masked-view`)"
- RNConfigReader (from `../node_modules/react-native-config-reader`) - RNConfigReader (from `../node_modules/react-native-config-reader`)
- "RNCPicker (from `../node_modules/@react-native-community/picker`)" - "RNCPicker (from `../node_modules/@react-native-community/picker`)"
@ -879,6 +882,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-bootsplash" :path: "../node_modules/react-native-bootsplash"
RNCAsyncStorage: RNCAsyncStorage:
:path: "../node_modules/@react-native-community/async-storage" :path: "../node_modules/@react-native-community/async-storage"
RNCClipboard:
:path: "../node_modules/@react-native-clipboard/clipboard"
RNCMaskedView: RNCMaskedView:
:path: "../node_modules/@react-native-community/masked-view" :path: "../node_modules/@react-native-community/masked-view"
RNConfigReader: RNConfigReader:
@ -950,7 +955,7 @@ SPEC CHECKSUMS:
EXVideoThumbnails: 442c3abadb51a81551a3b53705b7560de390e6f7 EXVideoThumbnails: 442c3abadb51a81551a3b53705b7560de390e6f7
EXWebBrowser: 76783ba5dcb8699237746ecf41a9643d428a4cc5 EXWebBrowser: 76783ba5dcb8699237746ecf41a9643d428a4cc5
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
FBReactNativeSpec: 110d69378fce79af38271c39894b59fec7890221 FBReactNativeSpec: 686ac17e193dcf7d5df4d772b224504dd2f3ad81
Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892 Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892
FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4 FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4
FirebaseCore: 8cd4f8ea22075e0ee582849b1cf79d8816506085 FirebaseCore: 8cd4f8ea22075e0ee582849b1cf79d8816506085
@ -1024,6 +1029,7 @@ SPEC CHECKSUMS:
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNBootSplash: 4844706cbb56a3270556c9b94e59dedadccd47e4 RNBootSplash: 4844706cbb56a3270556c9b94e59dedadccd47e4
RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398 RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398
RNCClipboard: cc054ad1e8a33d2a74cd13e565588b4ca928d8fd
RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489 RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489
RNConfigReader: 396da6a6444182a76e8ae0930b9436c7575045cb RNConfigReader: 396da6a6444182a76e8ae0930b9436c7575045cb
RNCPicker: 914b557e20b3b8317b084aca9ff4b4edb95f61e4 RNCPicker: 914b557e20b3b8317b084aca9ff4b4edb95f61e4

View File

@ -1672,7 +1672,7 @@
INFOPLIST_FILE = NotificationService/Info.plist; INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 11.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.26.1; MARKETING_VERSION = 4.26.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;
@ -1709,7 +1709,7 @@
INFOPLIST_FILE = NotificationService/Info.plist; INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 11.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.26.1; MARKETING_VERSION = 4.26.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";

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.26.1</string> <string>4.26.2</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>

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.26.1</string> <string>4.26.2</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>KeychainGroup</key> <key>KeychainGroup</key>

33
jest.setup.js Normal file
View File

@ -0,0 +1,33 @@
import mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock.js';
jest.mock('@react-native-clipboard/clipboard', () => mockClipboard);
jest.mock('react-native-mmkv-storage', () => ({
Loader: jest.fn().mockImplementation(() => ({
setProcessingMode: jest.fn().mockImplementation(() => ({
withEncryption: jest.fn().mockImplementation(() => ({
initialize: jest.fn()
}))
}))
})),
create: jest.fn(),
MODES: { MULTI_PROCESS: '' }
}));
jest.mock('rn-fetch-blob', () => ({
fs: {
dirs: {
DocumentDir: '/data/com.rocket.chat/documents',
DownloadDir: '/data/com.rocket.chat/downloads'
},
exists: jest.fn(() => null)
},
fetch: jest.fn(() => null),
config: jest.fn(() => null)
}));
jest.mock('react-native-file-viewer', () => ({
open: jest.fn(() => null)
}));
jest.mock('./app/lib/database', () => jest.fn(() => null));

View File

@ -1,6 +1,6 @@
{ {
"name": "rocket-chat-reactnative", "name": "rocket-chat-reactnative",
"version": "4.26.1", "version": "4.26.2",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "react-native start", "start": "react-native start",
@ -30,6 +30,7 @@
"@bugsnag/react-native": "^7.10.5", "@bugsnag/react-native": "^7.10.5",
"@codler/react-native-keyboard-aware-scroll-view": "^1.0.1", "@codler/react-native-keyboard-aware-scroll-view": "^1.0.1",
"@nozbe/watermelondb": "0.23.0", "@nozbe/watermelondb": "0.23.0",
"@react-native-clipboard/clipboard": "^1.8.5",
"@react-native-community/art": "^1.2.0", "@react-native-community/art": "^1.2.0",
"@react-native-community/async-storage": "1.12.1", "@react-native-community/async-storage": "1.12.1",
"@react-native-community/blur": "^3.6.0", "@react-native-community/blur": "^3.6.0",
@ -208,7 +209,8 @@
"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js" "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
}, },
"setupFilesAfterEnv": [ "setupFilesAfterEnv": [
"@testing-library/jest-native/extend-expect" "@testing-library/jest-native/extend-expect",
"./jest.setup.js"
] ]
}, },
"snyk": true, "snyk": true,

View File

@ -23,7 +23,7 @@ index 602d51d..920d975 100644
public String getName() { public String getName() {
return "RNFetchBlob"; return "RNFetchBlob";
diff --git a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m diff --git a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m
index cdbe6b1..1699c6c 100644 index cdbe6b1..c0ce9bd 100644
--- a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m --- a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m
+++ b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m +++ b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m
@@ -15,6 +15,9 @@ @@ -15,6 +15,9 @@
@ -36,7 +36,7 @@ index cdbe6b1..1699c6c 100644
typedef NS_ENUM(NSUInteger, ResponseFormat) { typedef NS_ENUM(NSUInteger, ResponseFormat) {
UTF8, UTF8,
@@ -450,16 +453,107 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) { @@ -450,16 +453,108 @@ - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSen
} }
} }
@ -106,14 +106,14 @@ index cdbe6b1..1699c6c 100644
+ while (*utf8) [hex appendFormat:@"%02X", *utf8++ & 0x00FF]; + while (*utf8) [hex appendFormat:@"%02X", *utf8++ & 0x00FF];
+ +
+ return [[NSString stringWithFormat:@"%@", hex] lowercaseString]; + return [[NSString stringWithFormat:@"%@", hex] lowercaseString];
+} }
+
+-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler +-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
+{ +{
+ NSString *host = challenge.protectionSpace.host; + NSString *host = challenge.protectionSpace.host;
+ +
+ // Read the clientSSL info from MMKV + // Read the clientSSL info from MMKV
+ __block NSDictionary *clientSSL; + __block NSString *clientSSL;
+ SecureStorage *secureStorage = [[SecureStorage alloc] init]; + SecureStorage *secureStorage = [[SecureStorage alloc] init];
+ +
+ // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31 + // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31
@ -123,21 +123,22 @@ index cdbe6b1..1699c6c 100644
+ return; + return;
+ } + }
+ +
+ NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
+ NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding]; + NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding];
+ MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess]; + MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess];
+ clientSSL = [mmkv getObjectOfClass:[NSDictionary class] forKey:host]; + clientSSL = [mmkv getStringForKey:host];
+ +
+ NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + if ([clientSSL length] != 0) {
+ + NSData *data = [clientSSL dataUsingEncoding:NSUTF8StringEncoding];
+ if (clientSSL != (id)[NSNull null]) { + id dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
+ NSString *path = [clientSSL objectForKey:@"path"]; + NSString *path = [dict objectForKey:@"path"];
+ NSString *password = [clientSSL objectForKey:@"password"]; + NSString *password = [dict objectForKey:@"password"];
+ credential = [self getUrlCredential:challenge path:path password:password]; + credential = [self getUrlCredential:challenge path:path password:password];
+ } + }
+ +
+ completionHandler(NSURLSessionAuthChallengeUseCredential, credential); + completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} +}
+
+// - (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler +// - (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler
+// { +// {
+// if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) { +// if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) {

File diff suppressed because one or more lines are too long

901
yarn.lock

File diff suppressed because it is too large Load Diff