Merge 4.24.0 into master (#3648)

This commit is contained in:
Diego Mello 2022-01-24 17:12:36 -03:00 committed by GitHub
parent 857394aba8
commit 04db33a61e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
211 changed files with 2254 additions and 1364 deletions

View File

@ -17,14 +17,15 @@ module.exports = {
legacyDecorators: true legacyDecorators: true
} }
}, },
plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel'], plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel', 'jest'],
env: { env: {
browser: true, browser: true,
commonjs: true, commonjs: true,
es6: true, es6: true,
node: true, node: true,
jquery: true, jquery: true,
mocha: true mocha: true,
'jest/globals': true
}, },
rules: { rules: {
'import/extensions': [ 'import/extensions': [

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.23.0" versionName "4.24.0"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
if (!isFoss) { if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]

View File

@ -11,9 +11,12 @@ import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Promise;
import java.net.Socket; import java.net.Socket;
import java.security.KeyStore;
import java.security.Principal; import java.security.Principal;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedKeyManager;
import java.security.PrivateKey; import java.security.PrivateKey;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
@ -21,11 +24,12 @@ import javax.net.ssl.X509TrustManager;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import java.lang.InterruptedException;
import android.app.Activity; import android.app.Activity;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import android.security.KeyChain; import android.security.KeyChain;
import android.security.KeyChainAliasCallback; import android.security.KeyChainAliasCallback;
import java.util.Arrays;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.RNFetchBlob.RNFetchBlob; import com.RNFetchBlob.RNFetchBlob;
@ -52,8 +56,9 @@ public class SSLPinningModule extends ReactContextBaseJavaModule implements KeyC
public void apply(OkHttpClient.Builder builder) { public void apply(OkHttpClient.Builder builder) {
if (alias != null) { if (alias != null) {
SSLSocketFactory sslSocketFactory = getSSLFactory(alias); SSLSocketFactory sslSocketFactory = getSSLFactory(alias);
X509TrustManager trustManager = getTrustManagerFactory();
if (sslSocketFactory != null) { if (sslSocketFactory != null) {
builder.sslSocketFactory(sslSocketFactory); builder.sslSocketFactory(sslSocketFactory, trustManager);
} }
} }
} }
@ -68,8 +73,9 @@ public class SSLPinningModule extends ReactContextBaseJavaModule implements KeyC
if (alias != null) { if (alias != null) {
SSLSocketFactory sslSocketFactory = getSSLFactory(alias); SSLSocketFactory sslSocketFactory = getSSLFactory(alias);
X509TrustManager trustManager = getTrustManagerFactory();
if (sslSocketFactory != null) { if (sslSocketFactory != null) {
builder.sslSocketFactory(sslSocketFactory); builder.sslSocketFactory(sslSocketFactory, trustManager);
} }
} }
@ -162,25 +168,9 @@ public class SSLPinningModule extends ReactContextBaseJavaModule implements KeyC
} }
}; };
final TrustManager[] trustAllCerts = new TrustManager[] { final X509TrustManager trustManager = getTrustManagerFactory();
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return certChain;
}
}
};
final SSLContext sslContext = SSLContext.getInstance("TLS"); final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(new KeyManager[]{keyManager}, trustAllCerts, new java.security.SecureRandom()); sslContext.init(new KeyManager[]{keyManager}, new TrustManager[]{trustManager}, new java.security.SecureRandom());
SSLContext.setDefault(sslContext); SSLContext.setDefault(sslContext);
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
@ -190,4 +180,19 @@ public class SSLPinningModule extends ReactContextBaseJavaModule implements KeyC
return null; return null;
} }
} }
public static X509TrustManager getTrustManagerFactory() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
final X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
return trustManager;
} catch (Exception e) {
return null;
}
}
} }

View File

@ -3,7 +3,7 @@ import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { SetUsernameStackParamList, StackParamList } from './navigationTypes'; import { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes';
import Navigation from './lib/Navigation'; import Navigation from './lib/Navigation';
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation'; import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app'; import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app';

View File

@ -2,8 +2,8 @@ const REQUEST = 'REQUEST';
const SUCCESS = 'SUCCESS'; const SUCCESS = 'SUCCESS';
const FAILURE = 'FAILURE'; const FAILURE = 'FAILURE';
const defaultTypes = [REQUEST, SUCCESS, FAILURE]; const defaultTypes = [REQUEST, SUCCESS, FAILURE];
function createRequestTypes(base, types = defaultTypes) { function createRequestTypes(base = {}, types = defaultTypes): Record<any, any> {
const res = {}; const res: Record<any, any> = {};
types.forEach(type => (res[type] = `${base}_${type}`)); types.forEach(type => (res[type] = `${base}_${type}`));
return res; return res;
} }

View File

@ -1,8 +0,0 @@
import { SET_ACTIVE_USERS } from './actionsTypes';
export function setActiveUsers(activeUsers) {
return {
type: SET_ACTIVE_USERS,
activeUsers
};
}

View File

@ -0,0 +1,15 @@
import { Action } from 'redux';
import { IActiveUsers } from '../reducers/activeUsers';
import { SET_ACTIVE_USERS } from './actionsTypes';
export interface ISetActiveUsers extends Action {
activeUsers: IActiveUsers;
}
export type TActionActiveUsers = ISetActiveUsers;
export const setActiveUsers = (activeUsers: IActiveUsers): ISetActiveUsers => ({
type: SET_ACTIVE_USERS,
activeUsers
});

View File

@ -1,28 +0,0 @@
import * as types from './actionsTypes';
export function addUser(user) {
return {
type: types.SELECTED_USERS.ADD_USER,
user
};
}
export function removeUser(user) {
return {
type: types.SELECTED_USERS.REMOVE_USER,
user
};
}
export function reset() {
return {
type: types.SELECTED_USERS.RESET
};
}
export function setLoading(loading) {
return {
type: types.SELECTED_USERS.SET_LOADING,
loading
};
}

View File

@ -0,0 +1,43 @@
import { Action } from 'redux';
import { ISelectedUser } from '../reducers/selectedUsers';
import * as types from './actionsTypes';
type TUser = {
user: ISelectedUser;
};
type TAction = Action & TUser;
interface ISetLoading extends Action {
loading: boolean;
}
export type TActionSelectedUsers = TAction & ISetLoading;
export function addUser(user: ISelectedUser): TAction {
return {
type: types.SELECTED_USERS.ADD_USER,
user
};
}
export function removeUser(user: ISelectedUser): TAction {
return {
type: types.SELECTED_USERS.REMOVE_USER,
user
};
}
export function reset(): Action {
return {
type: types.SELECTED_USERS.RESET
};
}
export function setLoading(loading: boolean): ISetLoading {
return {
type: types.SELECTED_USERS.SET_LOADING,
loading
};
}

View File

@ -1,2 +0,0 @@
export const DISPLAY_MODE_CONDENSED = 'condensed';
export const DISPLAY_MODE_EXPANDED = 'expanded';

View File

@ -0,0 +1,9 @@
export enum DisplayMode {
Condensed = 'condensed',
Expanded = 'expanded'
}
export enum SortBy {
Alphabetical = 'alphabetical',
Activity = 'activity'
}

View File

@ -1,7 +1,8 @@
import React from 'react';
import { TouchableOpacity } from 'react-native'; import { TouchableOpacity } from 'react-native';
import { isAndroid } from '../../utils/deviceInfo'; import { isAndroid } from '../../utils/deviceInfo';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
// Taken from https://github.com/rgommezz/react-native-scroll-bottom-sheet#touchables // Taken from https://github.com/rgommezz/react-native-scroll-bottom-sheet#touchables
export const Button = isAndroid ? Touch : TouchableOpacity; export const Button: typeof React.Component = isAndroid ? Touch : TouchableOpacity;

View File

@ -5,6 +5,7 @@ import Touchable from 'react-native-platform-touchable';
import { settings as RocketChatSettings } from '@rocket.chat/sdk'; import { settings as RocketChatSettings } from '@rocket.chat/sdk';
import { avatarURL } from '../../utils/avatar'; import { avatarURL } from '../../utils/avatar';
import { SubscriptionType } from '../../definitions/ISubscription';
import Emoji from '../markdown/Emoji'; import Emoji from '../markdown/Emoji';
import { IAvatar } from './interfaces'; import { IAvatar } from './interfaces';
@ -27,8 +28,8 @@ const Avatar = React.memo(
text, text,
size = 25, size = 25,
borderRadius = 4, borderRadius = 4,
type = 'd' type = SubscriptionType.DIRECT
}: Partial<IAvatar>) => { }: IAvatar) => {
if ((!text && !avatar && !emoji && !rid) || !server) { if ((!text && !avatar && !emoji && !rid) || !server) {
return null; return null;
} }

View File

@ -7,17 +7,17 @@ import { getUserSelector } from '../../selectors/login';
import Avatar from './Avatar'; import Avatar from './Avatar';
import { IAvatar } from './interfaces'; import { IAvatar } from './interfaces';
class AvatarContainer extends React.Component<Partial<IAvatar>, any> { class AvatarContainer extends React.Component<IAvatar, any> {
private mounted: boolean; private mounted: boolean;
private subscription!: any; private subscription: any;
static defaultProps = { static defaultProps = {
text: '', text: '',
type: 'd' type: 'd'
}; };
constructor(props: Partial<IAvatar>) { constructor(props: IAvatar) {
super(props); super(props);
this.mounted = false; this.mounted = false;
this.state = { avatarETag: '' }; this.state = { avatarETag: '' };
@ -55,7 +55,7 @@ class AvatarContainer extends React.Component<Partial<IAvatar>, any> {
try { try {
if (this.isDirect) { if (this.isDirect) {
const { text } = this.props; const { text } = this.props;
const [user] = await usersCollection.query(Q.where('username', text!)).fetch(); const [user] = await usersCollection.query(Q.where('username', text)).fetch();
record = user; record = user;
} else { } else {
const { rid } = this.props; const { rid } = this.props;
@ -82,7 +82,7 @@ class AvatarContainer extends React.Component<Partial<IAvatar>, any> {
render() { render() {
const { avatarETag } = this.state; const { avatarETag } = this.state;
const { serverVersion } = this.props; const { serverVersion } = this.props;
return <Avatar avatarETag={avatarETag} serverVersion={serverVersion} {...this.props} />; return <Avatar {...this.props} avatarETag={avatarETag} serverVersion={serverVersion} />;
} }
} }

View File

@ -1,23 +1,23 @@
export interface IAvatar { export interface IAvatar {
server: string; server?: string;
style: any; style?: any;
text: string; text: string;
avatar: string; avatar?: string;
emoji: string; emoji?: string;
size: number; size?: number;
borderRadius: number; borderRadius?: number;
type: string; type?: string;
children: JSX.Element; children?: JSX.Element;
user: { user?: {
id: string; id?: string;
token: string; token?: string;
}; };
theme: string; theme?: string;
onPress(): void; onPress?: () => void;
getCustomEmoji(): any; getCustomEmoji?: () => any;
avatarETag: string; avatarETag?: string;
isStatic: boolean | string; isStatic?: boolean | string;
rid: string; rid?: string;
blockUnauthenticatedAccess: boolean; blockUnauthenticatedAccess?: boolean;
serverVersion: string; serverVersion?: string;
} }

View File

@ -6,9 +6,9 @@ import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
interface IBackgroundContainer { interface IBackgroundContainer {
text: string; text?: string;
theme: string; theme?: string;
loading: boolean; loading?: boolean;
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -35,8 +35,8 @@ const styles = StyleSheet.create({
const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => ( const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => (
<View style={styles.container}> <View style={styles.container}>
<ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} /> <ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} />
{text ? <Text style={[styles.text, { color: themes[theme].auxiliaryTintColor }]}>{text}</Text> : null} {text ? <Text style={[styles.text, { color: themes[theme!].auxiliaryTintColor }]}>{text}</Text> : null}
{loading ? <ActivityIndicator style={styles.text} color={themes[theme].auxiliaryTintColor} /> : null} {loading ? <ActivityIndicator style={styles.text} color={themes[theme!].auxiliaryTintColor} /> : null}
</View> </View>
); );

View File

@ -29,9 +29,9 @@ export const CloseModal = React.memo(
export const CancelModal = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => ( export const CancelModal = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => (
<Container left> <Container left>
{isIOS ? ( {isIOS ? (
<Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} /> <Item title={I18n.t('Cancel')} onPress={onPress!} testID={testID} />
) : ( ) : (
<Item iconName='close' onPress={onPress} testID={testID} /> <Item iconName='close' onPress={onPress!} testID={testID} />
)} )}
</Container> </Container>
)); ));
@ -39,19 +39,19 @@ export const CancelModal = React.memo(({ onPress, testID }: Partial<IHeaderButto
// Right // Right
export const More = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => ( export const More = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => (
<Container> <Container>
<Item iconName='kebab' onPress={onPress} testID={testID} /> <Item iconName='kebab' onPress={onPress!} testID={testID} />
</Container> </Container>
)); ));
export const Download = React.memo(({ onPress, testID, ...props }: Partial<IHeaderButtonCommon>) => ( export const Download = React.memo(({ onPress, testID, ...props }: Partial<IHeaderButtonCommon>) => (
<Container> <Container>
<Item iconName='download' onPress={onPress} testID={testID} {...props} /> <Item iconName='download' onPress={onPress!} testID={testID} {...props} />
</Container> </Container>
)); ));
export const Preferences = React.memo(({ onPress, testID, ...props }: Partial<IHeaderButtonCommon>) => ( export const Preferences = React.memo(({ onPress, testID, ...props }: Partial<IHeaderButtonCommon>) => (
<Container> <Container>
<Item iconName='settings' onPress={onPress} testID={testID} {...props} /> <Item iconName='settings' onPress={onPress!} testID={testID} {...props} />
</Container> </Container>
)); ));

View File

@ -8,12 +8,12 @@ import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
interface IHeaderButtonItem { interface IHeaderButtonItem {
title: string; title?: string;
iconName: string; iconName?: string;
onPress(): void; onPress: <T>(arg: T) => void;
testID: string; testID?: string;
theme: string; theme?: string;
badge(): void; badge?(): void;
} }
export const BUTTON_HIT_SLOP = { export const BUTTON_HIT_SLOP = {
@ -44,9 +44,9 @@ const Item = ({ title, iconName, onPress, testID, theme, badge }: IHeaderButtonI
<Touchable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} style={styles.container}> <Touchable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} style={styles.container}>
<> <>
{iconName ? ( {iconName ? (
<CustomIcon name={iconName} size={24} color={themes[theme].headerTintColor} /> <CustomIcon name={iconName} size={24} color={themes[theme!].headerTintColor} />
) : ( ) : (
<Text style={[styles.title, { color: themes[theme].headerTintColor }]}>{title}</Text> <Text style={[styles.title, { color: themes[theme!].headerTintColor }]}>{title}</Text>
)} )}
{badge ? badge() : null} {badge ? badge() : null}
</> </>

View File

@ -11,10 +11,10 @@ const styles = StyleSheet.create({
}); });
interface IListContainer { interface IListContainer {
children: JSX.Element; children: React.ReactNode;
testID?: string;
} }
const ListContainer = React.memo(({ children, ...props }: IListContainer) => ( const ListContainer = React.memo(({ children, ...props }: IListContainer) => (
// @ts-ignore
<ScrollView <ScrollView
contentContainerStyle={styles.container} contentContainerStyle={styles.container}
scrollIndicatorInsets={{ right: 1 }} // https://github.com/facebook/react-native/issues/26610#issuecomment-539843444 scrollIndicatorInsets={{ right: 1 }} // https://github.com/facebook/react-native/issues/26610#issuecomment-539843444

View File

@ -20,13 +20,13 @@ const styles = StyleSheet.create({
interface IListHeader { interface IListHeader {
title: string; title: string;
theme: string; theme?: string;
translateTitle: boolean; translateTitle?: boolean;
} }
const ListHeader = React.memo(({ title, theme, translateTitle = true }: IListHeader) => ( const ListHeader = React.memo(({ title, theme, translateTitle = true }: IListHeader) => (
<View style={styles.container}> <View style={styles.container}>
<Text style={[styles.title, { color: themes[theme].infoText }]} numberOfLines={1}> <Text style={[styles.title, { color: themes[theme!].infoText }]} numberOfLines={1}>
{translateTitle ? I18n.t(title) : title} {translateTitle ? I18n.t(title) : title}
</Text> </Text>
</View> </View>

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
@ -7,11 +7,11 @@ import { withTheme } from '../../theme';
import { ICON_SIZE } from './constants'; import { ICON_SIZE } from './constants';
interface IListIcon { interface IListIcon {
theme: string; theme?: string;
name: string; name: string;
color: string; color?: string;
style: object; style?: StyleProp<ViewStyle>;
testID: string; testID?: string;
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -23,7 +23,7 @@ const styles = StyleSheet.create({
const ListIcon = React.memo(({ theme, name, color, style, testID }: IListIcon) => ( const ListIcon = React.memo(({ theme, name, color, style, testID }: IListIcon) => (
<View style={[styles.icon, style]}> <View style={[styles.icon, style]}>
<CustomIcon name={name} color={color ?? themes[theme].auxiliaryText} size={ICON_SIZE} testID={testID} /> <CustomIcon name={name} color={color ?? themes[theme!].auxiliaryText} size={ICON_SIZE} testID={testID} />
</View> </View>
)); ));

View File

@ -20,13 +20,13 @@ const styles = StyleSheet.create({
interface IListHeader { interface IListHeader {
info: string; info: string;
theme: string; theme?: string;
translateInfo: boolean; translateInfo?: boolean;
} }
const ListInfo = React.memo(({ info, theme, translateInfo = true }: IListHeader) => ( const ListInfo = React.memo(({ info, theme, translateInfo = true }: IListHeader) => (
<View style={styles.container}> <View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].infoText }]}>{translateInfo ? I18n.t(info) : info}</Text> <Text style={[styles.text, { color: themes[theme!].infoText }]}>{translateInfo ? I18n.t(info) : info}</Text>
</View> </View>
)); ));

View File

@ -56,11 +56,11 @@ const styles = StyleSheet.create({
interface IListItemContent { interface IListItemContent {
title?: string; title?: string;
subtitle?: string; subtitle?: string;
left?: Function; left?: () => JSX.Element | null;
right?: Function; right?: () => JSX.Element | null;
disabled?: boolean; disabled?: boolean;
testID?: string; testID?: string;
theme: string; theme?: string;
color?: string; color?: string;
translateTitle?: boolean; translateTitle?: boolean;
translateSubtitle?: boolean; translateSubtitle?: boolean;
@ -89,15 +89,15 @@ const Content = React.memo(
{left ? <View style={styles.leftContainer}>{left()}</View> : null} {left ? <View style={styles.leftContainer}>{left()}</View> : null}
<View style={styles.textContainer}> <View style={styles.textContainer}>
<View style={styles.textAlertContainer}> <View style={styles.textAlertContainer}>
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}> <Text style={[styles.title, { color: color || themes[theme!].titleText }]} numberOfLines={1}>
{translateTitle ? I18n.t(title) : title} {translateTitle ? I18n.t(title) : title}
</Text> </Text>
{alert ? ( {alert ? (
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' /> <CustomIcon style={[styles.alertIcon, { color: themes[theme!].dangerColor }]} size={ICON_SIZE} name='info' />
) : null} ) : null}
</View> </View>
{subtitle ? ( {subtitle ? (
<Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> <Text style={[styles.subtitle, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
{translateSubtitle ? I18n.t(subtitle) : subtitle} {translateSubtitle ? I18n.t(subtitle) : subtitle}
</Text> </Text>
) : null} ) : null}
@ -112,38 +112,39 @@ const Content = React.memo(
) )
); );
interface IListItemButton { interface IListButtonPress {
onPress?: Function;
}
interface IListItemButton extends IListButtonPress {
title?: string; title?: string;
onPress: Function;
disabled?: boolean; disabled?: boolean;
theme: string; theme?: string;
backgroundColor: string; backgroundColor?: string;
underlayColor?: string; underlayColor?: string;
} }
const Button = React.memo(({ onPress, backgroundColor, underlayColor, ...props }: IListItemButton) => ( const Button = React.memo<IListItemButton>(({ onPress, backgroundColor, underlayColor, ...props }: IListItemButton) => (
<Touch <Touch
onPress={() => onPress(props.title)} onPress={() => onPress!(props.title)}
style={{ backgroundColor: backgroundColor || themes[props.theme].backgroundColor }} style={{ backgroundColor: backgroundColor || themes[props.theme!].backgroundColor }}
underlayColor={underlayColor} underlayColor={underlayColor}
enabled={!props.disabled} enabled={!props.disabled}
theme={props.theme}> theme={props.theme!}>
<Content {...props} /> <Content {...props} />
</Touch> </Touch>
)); ));
interface IListItem { interface IListItem extends IListItemContent, IListButtonPress {
onPress: Function; backgroundColor?: string;
theme: string;
backgroundColor: string;
} }
const ListItem = React.memo(({ ...props }: IListItem) => { const ListItem = React.memo<IListItem>(({ ...props }: IListItem) => {
if (props.onPress) { if (props.onPress) {
return <Button {...props} />; return <Button {...props} />;
} }
return ( return (
<View style={{ backgroundColor: props.backgroundColor || themes[props.theme].backgroundColor }}> <View style={{ backgroundColor: props.backgroundColor || themes[props.theme!].backgroundColor }}>
<Content {...props} /> <Content {...props} />
</View> </View>
); );

View File

@ -11,9 +11,9 @@ const styles = StyleSheet.create({
}); });
interface IListSection { interface IListSection {
children: JSX.Element; children: React.ReactNode;
title: string; title?: string;
translateTitle: boolean; translateTitle?: boolean;
} }
const ListSection = React.memo(({ children, title, translateTitle }: IListSection) => ( const ListSection = React.memo(({ children, title, translateTitle }: IListSection) => (

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
@ -11,12 +11,12 @@ const styles = StyleSheet.create({
}); });
interface IListSeparator { interface IListSeparator {
style: object; style?: ViewStyle;
theme: string; theme?: string;
} }
const ListSeparator = React.memo(({ style, theme }: IListSeparator) => ( const ListSeparator = React.memo(({ style, theme }: IListSeparator) => (
<View style={[styles.separator, style, { backgroundColor: themes[theme].separatorColor }]} /> <View style={[styles.separator, style, { backgroundColor: themes[theme!].separatorColor }]} />
)); ));
ListSeparator.displayName = 'List.Separator'; ListSeparator.displayName = 'List.Separator';

View File

@ -19,7 +19,7 @@ const styles = StyleSheet.create({
interface ILoadingProps { interface ILoadingProps {
visible: boolean; visible: boolean;
theme: string; theme?: string;
} }
class Loading extends React.PureComponent<ILoadingProps, any> { class Loading extends React.PureComponent<ILoadingProps, any> {
@ -97,7 +97,7 @@ class Loading extends React.PureComponent<ILoadingProps, any> {
const opacityAnimation = opacity.interpolate({ const opacityAnimation = opacity.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity], outputRange: [0, themes[theme!].backdropOpacity],
extrapolate: 'clamp' extrapolate: 'clamp'
}); });
@ -109,7 +109,7 @@ class Loading extends React.PureComponent<ILoadingProps, any> {
{ {
// @ts-ignore // @ts-ignore
...StyleSheet.absoluteFill, ...StyleSheet.absoluteFill,
backgroundColor: themes[theme].backdropColor, backgroundColor: themes[theme!].backdropColor,
opacity: opacityAnimation opacity: opacityAnimation
} }
]} ]}

View File

@ -17,7 +17,7 @@ interface IHeader {
server: string; server: string;
message: object; message: object;
isMasterDetail: boolean; isMasterDetail: boolean;
theme: string; theme?: string;
} }
interface THeaderItem { interface THeaderItem {
@ -117,19 +117,19 @@ const Header = React.memo(({ handleReaction, server, message, isMasterDetail, th
const onReaction = ({ emoji }: { emoji: IEmoji }) => handleReaction(emoji, message); const onReaction = ({ emoji }: { emoji: IEmoji }) => handleReaction(emoji, message);
const renderItem = useCallback( const renderItem = useCallback(
({ item }) => <HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />, ({ item }) => <HeaderItem item={item} onReaction={onReaction} server={server} theme={theme!} />,
[] []
); );
const renderFooter = useCallback(() => <HeaderFooter onReaction={onReaction} theme={theme} />, []); const renderFooter = useCallback(() => <HeaderFooter onReaction={onReaction} theme={theme!} />, []);
return ( return (
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}> <View style={[styles.container, { backgroundColor: themes[theme!].focusedBackground }]}>
<FlatList <FlatList
data={items} data={items}
renderItem={renderItem} renderItem={renderItem}
ListFooterComponent={renderFooter} ListFooterComponent={renderFooter}
style={{ backgroundColor: themes[theme].focusedBackground }} style={{ backgroundColor: themes[theme!].focusedBackground }}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
scrollEnabled={false} scrollEnabled={false}

View File

@ -18,7 +18,7 @@ import events from '../../utils/log/events';
interface IMessageActions { interface IMessageActions {
room: { room: {
rid: string | number; rid: string;
autoTranslateLanguage: any; autoTranslateLanguage: any;
autoTranslate: any; autoTranslate: any;
reactWhenReadOnly: any; reactWhenReadOnly: any;
@ -305,8 +305,6 @@ const MessageActions = React.memo(
}; };
const handleDelete = (message: any) => { const handleDelete = (message: any) => {
// TODO - migrate this function for ts when fix the lint erros
// @ts-ignore
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('You_will_not_be_able_to_recover_this_message'), message: I18n.t('You_will_not_be_able_to_recover_this_message'),
confirmationText: I18n.t('Delete'), confirmationText: I18n.t('Delete'),

View File

@ -14,7 +14,7 @@ interface IMessageBoxCommandsPreviewItem {
id: string; id: string;
value: string; value: string;
}; };
theme: string; theme?: string;
} }
const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => { const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => {
@ -37,7 +37,7 @@ const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => {
{loading ? <ActivityIndicator theme={theme} /> : null} {loading ? <ActivityIndicator theme={theme} /> : null}
</FastImage> </FastImage>
) : ( ) : (
<CustomIcon name='attach' size={36} color={themes[theme].actionTintColor} /> <CustomIcon name='attach' size={36} color={themes[theme!].actionTintColor} />
)} )}
</TouchableOpacity> </TouchableOpacity>
); );

View File

@ -10,7 +10,7 @@ import { withTheme } from '../../../theme';
interface IMessageBoxCommandsPreview { interface IMessageBoxCommandsPreview {
commandPreview: []; commandPreview: [];
showCommandPreview: boolean; showCommandPreview: boolean;
theme: string; theme?: string;
} }
const CommandsPreview = React.memo( const CommandsPreview = React.memo(
@ -21,7 +21,7 @@ const CommandsPreview = React.memo(
return ( return (
<FlatList <FlatList
testID='commandbox-container' testID='commandbox-container'
style={[styles.mentionList, { backgroundColor: themes[theme].messageboxBackground }]} style={[styles.mentionList, { backgroundColor: themes[theme!].messageboxBackground }]}
data={commandPreview} data={commandPreview}
renderItem={({ item }) => <Item item={item} theme={theme} />} renderItem={({ item }) => <Item item={item} theme={theme} />}
keyExtractor={(item: any) => item.id} keyExtractor={(item: any) => item.id}

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Alert, Keyboard, NativeModules, Text, View } from 'react-native'; import { Alert, Keyboard, NativeModules, Text, View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard'; import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker from 'react-native-image-crop-picker'; import ImagePicker, { Image, ImageOrVideo } from 'react-native-image-crop-picker';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import DocumentPicker from 'react-native-document-picker'; import DocumentPicker from 'react-native-document-picker';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
@ -27,7 +27,7 @@ import LeftButtons from './LeftButtons';
// @ts-ignore // @ts-ignore
// eslint-disable-next-line import/extensions,import/no-unresolved // eslint-disable-next-line import/extensions,import/no-unresolved
import RightButtons from './RightButtons'; import RightButtons from './RightButtons';
import { isAndroid, isTablet } from '../../utils/deviceInfo'; import { isAndroid, isIOS, isTablet } from '../../utils/deviceInfo';
import { canUploadFile } from '../../utils/media'; import { canUploadFile } from '../../utils/media';
import EventEmiter from '../../utils/events'; import EventEmiter from '../../utils/events';
import { KEY_COMMAND, handleCommandShowUpload, handleCommandSubmit, handleCommandTyping } from '../../commands'; import { KEY_COMMAND, handleCommandShowUpload, handleCommandSubmit, handleCommandTyping } from '../../commands';
@ -54,15 +54,16 @@ if (isAndroid) {
const imagePickerConfig = { const imagePickerConfig = {
cropping: true, cropping: true,
compressImageQuality: 0.8,
avoidEmptySpaceAroundImage: false, avoidEmptySpaceAroundImage: false,
freeStyleCropEnabled: true freeStyleCropEnabled: true,
forceJpg: true
}; };
const libraryPickerConfig = { const libraryPickerConfig = {
multiple: true, multiple: true,
compressVideoPreset: 'Passthrough', compressVideoPreset: 'Passthrough',
mediaType: 'any' mediaType: 'any',
forceJpg: true
}; };
const videoPickerConfig = { const videoPickerConfig = {
@ -129,6 +130,18 @@ interface IMessageBoxState {
permissionToUpload: boolean; permissionToUpload: boolean;
} }
const forceJpgExtension = (attachment: ImageOrVideo) => {
if (isIOS && attachment.mime === 'image/jpeg' && attachment.filename) {
const regex = new RegExp(/.heic$/i);
if (attachment.filename.match(regex)) {
attachment.filename = attachment.filename.replace(regex, '.jpg');
} else {
attachment.filename += '.jpg';
}
}
return attachment;
};
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> { class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
private text: string; private text: string;
@ -692,7 +705,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
takePhoto = async () => { takePhoto = async () => {
logEvent(events.ROOM_BOX_ACTION_PHOTO); logEvent(events.ROOM_BOX_ACTION_PHOTO);
try { try {
const image = await ImagePicker.openCamera(this.imagePickerConfig); let image = (await ImagePicker.openCamera(this.imagePickerConfig)) as Image;
image = forceJpgExtension(image);
if (this.canUploadFile(image)) { if (this.canUploadFile(image)) {
this.openShareView([image]); this.openShareView([image]);
} }
@ -716,7 +730,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
chooseFromLibrary = async () => { chooseFromLibrary = async () => {
logEvent(events.ROOM_BOX_ACTION_LIBRARY); logEvent(events.ROOM_BOX_ACTION_LIBRARY);
try { try {
const attachments = await ImagePicker.openPicker(this.libraryPickerConfig); let attachments = (await ImagePicker.openPicker(this.libraryPickerConfig)) as ImageOrVideo[];
attachments = attachments.map(att => forceJpgExtension(att));
this.openShareView(attachments); this.openShareView(attachments);
} catch (e) { } catch (e) {
logEvent(events.ROOM_BOX_ACTION_LIBRARY_F); logEvent(events.ROOM_BOX_ACTION_LIBRARY_F);

View File

@ -7,28 +7,28 @@ import Touch from '../../../utils/touch';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
interface IPasscodeButton { interface IPasscodeButton {
text: string; text?: string;
icon: string; icon?: string;
theme: string; theme: string;
disabled: boolean; disabled?: boolean;
onPress: Function; onPress?: Function;
} }
const Button = React.memo(({ text, disabled, theme, onPress, icon }: Partial<IPasscodeButton>) => { const Button = React.memo(({ text, disabled, theme, onPress, icon }: IPasscodeButton) => {
const press = () => onPress && onPress(text!); const press = () => onPress && onPress(text);
return ( return (
<Touch <Touch
style={[styles.buttonView, { backgroundColor: 'transparent' }]} style={[styles.buttonView, { backgroundColor: 'transparent' }]}
underlayColor={themes[theme!].passcodeButtonActive} underlayColor={themes[theme].passcodeButtonActive}
rippleColor={themes[theme!].passcodeButtonActive} rippleColor={themes[theme].passcodeButtonActive}
enabled={!disabled} enabled={!disabled}
theme={theme} theme={theme}
onPress={press}> onPress={press}>
{icon ? ( {icon ? (
<CustomIcon name={icon} size={36} color={themes[theme!].passcodePrimary} /> <CustomIcon name={icon} size={36} color={themes[theme].passcodePrimary} />
) : ( ) : (
<Text style={[styles.buttonText, { color: themes[theme!].passcodePrimary }]}>{text}</Text> <Text style={[styles.buttonText, { color: themes[theme].passcodePrimary }]}>{text}</Text>
)} )}
</Touch> </Touch>
); );

View File

@ -68,11 +68,11 @@ interface IRoomHeader {
tmid: string; tmid: string;
teamMain: boolean; teamMain: boolean;
status: string; status: string;
theme: string; theme?: string;
usersTyping: []; usersTyping: [];
isGroupChat: boolean; isGroupChat: boolean;
parentTitle: string; parentTitle: string;
onPress: Function; onPress: () => void;
testID: string; testID: string;
} }
@ -164,7 +164,7 @@ const Header = React.memo(
renderFunc = () => ( renderFunc = () => (
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} /> <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
<Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> <Text style={[styles.subtitle, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
{parentTitle} {parentTitle}
</Text> </Text>
</View> </View>
@ -186,9 +186,15 @@ const Header = React.memo(
{tmid ? null : ( {tmid ? null : (
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} /> <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
)} )}
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} theme={theme} testID={testID} /> <HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} theme={theme!} testID={testID} />
</View> </View>
<SubTitle usersTyping={tmid ? [] : usersTyping} subtitle={subtitle} theme={theme} renderFunc={renderFunc} scale={scale} /> <SubTitle
usersTyping={tmid ? [] : usersTyping}
subtitle={subtitle}
theme={theme!}
renderFunc={renderFunc}
scale={scale}
/>
</TouchableOpacity> </TouchableOpacity>
); );
} }

View File

@ -13,7 +13,7 @@ interface IRoomHeaderContainerProps {
prid: string; prid: string;
tmid: string; tmid: string;
teamMain: boolean; teamMain: boolean;
usersTyping: string; usersTyping: [];
status: string; status: string;
statusText: string; statusText: string;
connecting: boolean; connecting: boolean;
@ -79,14 +79,12 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
teamMain, teamMain,
prid, prid,
tmid, tmid,
widthOffset,
status = 'offline', status = 'offline',
statusText, statusText,
connecting, connecting,
connected, connected,
usersTyping, usersTyping,
onPress, onPress,
roomUserId,
width, width,
height, height,
parentTitle, parentTitle,
@ -115,9 +113,6 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
width={width} width={width}
height={height} height={height}
usersTyping={usersTyping} usersTyping={usersTyping}
widthOffset={widthOffset}
roomUserId={roomUserId}
connecting={connecting}
parentTitle={parentTitle} parentTitle={parentTitle}
isGroupChat={isGroupChat} isGroupChat={isGroupChat}
testID={testID} testID={testID}

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet } from 'react-native'; import { StyleSheet, ViewStyle } from 'react-native';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import { STATUS_COLORS, themes } from '../constants/colors'; import { STATUS_COLORS, themes } from '../constants/colors';
@ -13,13 +13,13 @@ const styles = StyleSheet.create({
}); });
interface IRoomTypeIcon { interface IRoomTypeIcon {
theme: string; theme?: string;
type: string; type: string;
isGroupChat: boolean; isGroupChat?: boolean;
teamMain: boolean; teamMain?: boolean;
status: string; status?: string;
size: number; size?: number;
style: any; style?: ViewStyle;
} }
const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, teamMain, size = 16 }: IRoomTypeIcon) => { const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, teamMain, size = 16 }: IRoomTypeIcon) => {
@ -27,11 +27,13 @@ const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, team
return null; return null;
} }
const color = themes[theme].titleText; const color = themes[theme!].titleText;
const iconStyle = [styles.icon, { color }, style]; const iconStyle = [styles.icon, { color }, style];
if (type === 'd' && !isGroupChat) { if (type === 'd' && !isGroupChat) {
return <Status style={[iconStyle, { color: STATUS_COLORS[status] ?? STATUS_COLORS.offline }]} size={size} status={status} />; return (
<Status style={[iconStyle, { color: STATUS_COLORS[status!] ?? STATUS_COLORS.offline }]} size={size} status={status!} />
);
} }
// TODO: move this to a separate function // TODO: move this to a separate function

View File

@ -12,16 +12,16 @@ const styles = StyleSheet.create({
}); });
interface ISafeAreaView { interface ISafeAreaView {
testID: string; testID?: string;
theme: string; theme?: string;
vertical: boolean; vertical?: boolean;
style: object; style?: object;
children: JSX.Element; children: React.ReactNode;
} }
const SafeAreaView = React.memo(({ style, children, testID, theme, vertical = true, ...props }: ISafeAreaView) => ( const SafeAreaView = React.memo(({ style, children, testID, theme, vertical = true, ...props }: ISafeAreaView) => (
<SafeAreaContext <SafeAreaContext
style={[styles.view, { backgroundColor: themes[theme].auxiliaryBackground }, style]} style={[styles.view, { backgroundColor: themes[theme!].auxiliaryBackground }, style]}
edges={vertical ? ['right', 'left'] : undefined} edges={vertical ? ['right', 'left'] : undefined}
testID={testID} testID={testID}
{...props}> {...props}>

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, TextInputProps, View } from 'react-native'; import { NativeSyntheticEvent, StyleSheet, Text, TextInputFocusEventData, TextInputProps, View } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import TextInput from '../presentation/TextInput'; import TextInput from '../presentation/TextInput';
@ -45,13 +45,15 @@ const styles = StyleSheet.create({
}); });
interface ISearchBox { interface ISearchBox {
value?: string;
onChangeText: TextInputProps['onChangeText']; onChangeText: TextInputProps['onChangeText'];
onSubmitEditing: () => void; onSubmitEditing?: () => void;
hasCancel: boolean; hasCancel?: boolean;
onCancelPress: Function; onCancelPress?: Function;
theme: string; theme?: string;
inputRef: any; inputRef?: React.Ref<unknown>;
testID?: string; testID?: string;
onFocus?: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;
} }
const CancelButton = (onCancelPress: Function, theme: string) => ( const CancelButton = (onCancelPress: Function, theme: string) => (
@ -73,10 +75,10 @@ const SearchBox = ({
<View <View
style={[ style={[
styles.container, styles.container,
{ backgroundColor: isIOS ? themes[theme].headerBackground : themes[theme].headerSecondaryBackground } { backgroundColor: isIOS ? themes[theme!].headerBackground : themes[theme!].headerSecondaryBackground }
]}> ]}>
<View style={[styles.searchBox, { backgroundColor: themes[theme].searchboxBackground }]}> <View style={[styles.searchBox, { backgroundColor: themes[theme!].searchboxBackground }]}>
<CustomIcon name='search' size={14} color={themes[theme].auxiliaryText} /> <CustomIcon name='search' size={14} color={themes[theme!].auxiliaryText} />
<TextInput <TextInput
ref={inputRef} ref={inputRef}
autoCapitalize='none' autoCapitalize='none'
@ -90,11 +92,11 @@ const SearchBox = ({
underlineColorAndroid='transparent' underlineColorAndroid='transparent'
onChangeText={onChangeText} onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing} onSubmitEditing={onSubmitEditing}
theme={theme} theme={theme!}
{...props} {...props}
/> />
</View> </View>
{hasCancel ? CancelButton(onCancelPress, theme) : null} {hasCancel ? CancelButton(onCancelPress!, theme!) : null}
</View> </View>
); );

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
@ -20,9 +19,14 @@ const styles = StyleSheet.create({
} }
}); });
interface ISearchHeader {
theme?: string;
onSearchChangeText?: (text: string) => void;
}
// TODO: it might be useful to refactor this component for reusage // TODO: it might be useful to refactor this component for reusage
const SearchHeader = ({ theme, onSearchChangeText }) => { const SearchHeader = ({ theme, onSearchChangeText }: ISearchHeader) => {
const titleColorStyle = { color: themes[theme].headerTitleColor }; const titleColorStyle = { color: themes[theme!].headerTitleColor };
const isLight = theme === 'light'; const isLight = theme === 'light';
const { isLandscape } = useOrientation(); const { isLandscape } = useOrientation();
const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1; const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1;
@ -35,15 +39,11 @@ const SearchHeader = ({ theme, onSearchChangeText }) => {
style={[styles.title, isLight && titleColorStyle, { fontSize: titleFontSize }]} style={[styles.title, isLight && titleColorStyle, { fontSize: titleFontSize }]}
placeholder='Search' placeholder='Search'
onChangeText={onSearchChangeText} onChangeText={onSearchChangeText}
theme={theme} theme={theme!}
testID='thread-messages-view-search-header' testID='thread-messages-view-search-header'
/> />
</View> </View>
); );
}; };
SearchHeader.propTypes = {
theme: PropTypes.string,
onSearchChangeText: PropTypes.func
};
export default withTheme(SearchHeader); export default withTheme(SearchHeader);

View File

@ -5,9 +5,9 @@ import { themes } from '../constants/colors';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
interface IStatusBar { interface IStatusBar {
theme: string; theme?: string;
barStyle: any; barStyle?: any;
backgroundColor: string; backgroundColor?: string;
} }
const StatusBar = React.memo(({ theme, barStyle, backgroundColor }: IStatusBar) => { const StatusBar = React.memo(({ theme, barStyle, backgroundColor }: IStatusBar) => {
@ -17,7 +17,7 @@ const StatusBar = React.memo(({ theme, barStyle, backgroundColor }: IStatusBar)
barStyle = 'dark-content'; barStyle = 'dark-content';
} }
} }
return <StatusBarRN backgroundColor={backgroundColor ?? themes[theme].headerBackground} barStyle={barStyle} animated />; return <StatusBarRN backgroundColor={backgroundColor ?? themes[theme!].headerBackground} barStyle={barStyle} animated />;
}); });
export default withTheme(StatusBar); export default withTheme(StatusBar);

View File

@ -50,7 +50,7 @@ const styles = StyleSheet.create({
} }
}); });
interface IRCTextInputProps extends TextInputProps { export interface IRCTextInputProps extends TextInputProps {
label?: string; label?: string;
error?: { error?: {
error: any; error: any;

View File

@ -1,11 +1,12 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View, ViewStyle } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { TThreadModel } from '../definitions/IThread';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -40,33 +41,25 @@ const styles = StyleSheet.create({
}); });
interface IThreadDetails { interface IThreadDetails {
item: { item: Partial<TThreadModel>;
tcount: number | string;
replies: any;
id: string;
};
user: { user: {
id: string; id: string;
}; };
badgeColor: string; badgeColor?: string;
toggleFollowThread: Function; toggleFollowThread: Function;
style: object; style: ViewStyle;
theme: string; theme?: string;
} }
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, theme }: IThreadDetails) => { const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, theme }: IThreadDetails) => {
let { tcount } = item; let { tcount } = item;
if (tcount >= 1000) { if (tcount! >= 1000) {
tcount = '+999'; tcount = '+999';
} else if (tcount >= 100) {
tcount = '+99';
} }
let replies = item?.replies?.length ?? 0; let replies: number | string = item?.replies?.length ?? 0;
if (replies >= 1000) { if (replies >= 1000) {
replies = '+999'; replies = '+999';
} else if (replies >= 100) {
replies = '+99';
} }
const isFollowing = item.replies?.find((u: any) => u === user?.id); const isFollowing = item.replies?.find((u: any) => u === user?.id);
@ -75,15 +68,15 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
<View style={[styles.container, style]}> <View style={[styles.container, style]}>
<View style={styles.detailsContainer}> <View style={styles.detailsContainer}>
<View style={styles.detailContainer}> <View style={styles.detailContainer}>
<CustomIcon name='threads' size={24} color={themes[theme].auxiliaryText} /> <CustomIcon name='threads' size={24} color={themes[theme!].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> <Text style={[styles.detailText, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
{tcount} {tcount}
</Text> </Text>
</View> </View>
<View style={styles.detailContainer}> <View style={styles.detailContainer}>
<CustomIcon name='user' size={24} color={themes[theme].auxiliaryText} /> <CustomIcon name='user' size={24} color={themes[theme!].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> <Text style={[styles.detailText, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
{replies} {replies}
</Text> </Text>
</View> </View>
@ -95,7 +88,7 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
<CustomIcon <CustomIcon
size={24} size={24}
name={isFollowing ? 'notification' : 'notification-disabled'} name={isFollowing ? 'notification' : 'notification-disabled'}
color={themes[theme].auxiliaryTintColor} color={themes[theme!].auxiliaryTintColor}
/> />
</Touchable> </Touchable>
</View> </View>

View File

@ -22,7 +22,7 @@ const styles = StyleSheet.create({
export const LISTENER = 'Toast'; export const LISTENER = 'Toast';
interface IToastProps { interface IToastProps {
theme: string; theme?: string;
} }
class Toast extends React.Component<IToastProps, any> { class Toast extends React.Component<IToastProps, any> {
@ -61,8 +61,8 @@ class Toast extends React.Component<IToastProps, any> {
ref={this.getToastRef} ref={this.getToastRef}
// @ts-ignore // @ts-ignore
position='center' position='center'
style={[styles.toast, { backgroundColor: themes[theme].toastBackground }]} style={[styles.toast, { backgroundColor: themes[theme!].toastBackground }]}
textStyle={[styles.text, { color: themes[theme].buttonText }]} textStyle={[styles.text, { color: themes[theme!].buttonText }]}
opacity={0.9} opacity={0.9}
/> />
); );

View File

@ -19,7 +19,7 @@ import styles from './styles';
export const TWO_FACTOR = 'TWO_FACTOR'; export const TWO_FACTOR = 'TWO_FACTOR';
interface ITwoFactor { interface ITwoFactor {
theme: string; theme?: string;
isMasterDetail: boolean; isMasterDetail: boolean;
} }
@ -87,7 +87,7 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }: ITwoFactor) => {
setData({}); setData({});
}; };
const color = themes[theme].titleText; const color = themes[theme!].titleText;
return ( return (
<Modal <Modal
// @ts-ignore // @ts-ignore
@ -101,7 +101,7 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }: ITwoFactor) => {
style={[ style={[
styles.content, styles.content,
isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet], isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet],
{ backgroundColor: themes[theme].backgroundColor } { backgroundColor: themes[theme!].backgroundColor }
]}> ]}>
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text> <Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
{method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null} {method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
@ -128,7 +128,7 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }: ITwoFactor) => {
<Button <Button
title={I18n.t('Cancel')} title={I18n.t('Cancel')}
type='secondary' type='secondary'
backgroundColor={themes[theme].chatComponentBackground} backgroundColor={themes[theme!].chatComponentBackground}
style={styles.button} style={styles.button}
onPress={onCancel} onPress={onCancel}
theme={theme} theme={theme}

View File

@ -5,6 +5,7 @@ import Renderer from 'commonmark-react-renderer';
import removeMarkdown from 'remove-markdown'; import removeMarkdown from 'remove-markdown';
import { MarkdownAST } from '@rocket.chat/message-parser'; import { MarkdownAST } from '@rocket.chat/message-parser';
import { UserMention } from '../message/interfaces';
import shortnameToUnicode from '../../utils/shortnameToUnicode'; import shortnameToUnicode from '../../utils/shortnameToUnicode';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -23,14 +24,6 @@ import styles from './styles';
import { isValidURL } from '../../utils/url'; import { isValidURL } from '../../utils/url';
import NewMarkdown from './new'; import NewMarkdown from './new';
interface IUser {
_id: string;
username: string;
name: string;
}
type UserMention = Pick<IUser, '_id' | 'username' | 'name'>;
interface IMarkdownProps { interface IMarkdownProps {
msg: string; msg: string;
md: MarkdownAST; md: MarkdownAST;

View File

@ -30,7 +30,7 @@ interface IMessageAudioProps {
}; };
theme: string; theme: string;
getCustomEmoji: Function; getCustomEmoji: Function;
scale: number; scale?: number;
} }
interface IMessageAudioState { interface IMessageAudioState {

View File

@ -24,7 +24,7 @@ interface IMessageReaction {
} }
interface IMessageReactions { interface IMessageReactions {
reactions: object[]; reactions?: object[];
getCustomEmoji: Function; getCustomEmoji: Function;
theme: string; theme: string;
} }

View File

@ -13,6 +13,7 @@ import { themes } from '../../constants/colors';
import MessageContext from './Context'; import MessageContext from './Context';
import { fileDownloadAndPreview } from '../../utils/fileDownload'; import { fileDownloadAndPreview } from '../../utils/fileDownload';
import { formatAttachmentUrl } from '../../lib/utils'; import { formatAttachmentUrl } from '../../lib/utils';
import { IAttachment } from '../../definitions/IAttachment';
import RCActivityIndicator from '../ActivityIndicator'; import RCActivityIndicator from '../ActivityIndicator';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -90,43 +91,26 @@ const styles = StyleSheet.create({
} }
}); });
interface IMessageReplyAttachment {
author_name: string;
message_link: string;
ts: string;
text: string;
title: string;
short: boolean;
value: string;
title_link: string;
author_link: string;
type: string;
color: string;
description: string;
fields: IMessageReplyAttachment[];
thumb_url: string;
}
interface IMessageTitle { interface IMessageTitle {
attachment: Partial<IMessageReplyAttachment>; attachment: IAttachment;
timeFormat: string; timeFormat: string;
theme: string; theme: string;
} }
interface IMessageDescription { interface IMessageDescription {
attachment: Partial<IMessageReplyAttachment>; attachment: IAttachment;
getCustomEmoji: Function; getCustomEmoji: Function;
theme: string; theme: string;
} }
interface IMessageFields { interface IMessageFields {
attachment: Partial<IMessageReplyAttachment>; attachment: IAttachment;
theme: string; theme: string;
getCustomEmoji: Function; getCustomEmoji: Function;
} }
interface IMessageReply { interface IMessageReply {
attachment: IMessageReplyAttachment; attachment: IAttachment;
timeFormat: string; timeFormat: string;
index: number; index: number;
theme: string; theme: string;
@ -198,7 +182,7 @@ const Fields = React.memo(
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text> <Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
{/* @ts-ignore*/} {/* @ts-ignore*/}
<Markdown <Markdown
msg={field.value} msg={field.value!}
baseUrl={baseUrl} baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}

View File

@ -68,8 +68,8 @@ interface IMessageUrl {
} }
interface IMessageUrls { interface IMessageUrls {
urls: any; urls?: any;
theme: string; theme?: string;
} }
const UrlImage = React.memo( const UrlImage = React.memo(
@ -156,7 +156,7 @@ const Urls = React.memo(
return null; return null;
} }
return urls.map((url: any, index: number) => <Url url={url} key={url.url} index={index} theme={theme} />); return urls.map((url: any, index: number) => <Url url={url} key={url.url} index={index} theme={theme!} />);
}, },
(oldProps, newProps) => dequal(oldProps.urls, newProps.urls) && oldProps.theme === newProps.theme (oldProps, newProps) => dequal(oldProps.urls, newProps.urls) && oldProps.theme === newProps.theme
); );

View File

@ -38,17 +38,17 @@ const styles = StyleSheet.create({
}); });
interface IMessageUser { interface IMessageUser {
isHeader: boolean; isHeader?: boolean;
hasError: boolean; hasError?: boolean;
useRealName: boolean; useRealName: boolean;
author: { author?: {
_id: string; _id: string;
name: string; name?: string;
username: string; username?: string;
}; };
alias: string; alias?: string;
ts: Date; ts?: Date;
timeFormat: string; timeFormat?: string;
theme: string; theme: string;
navToRoomInfo: Function; navToRoomInfo: Function;
type: string; type: string;
@ -59,16 +59,16 @@ const User = React.memo(
if (isHeader || hasError) { if (isHeader || hasError) {
const navParam = { const navParam = {
t: 'd', t: 'd',
rid: author._id rid: author!._id
}; };
const { user } = useContext(MessageContext); const { user } = useContext(MessageContext);
const username = (useRealName && author.name) || author.username; const username = (useRealName && author!.name) || author!.username;
const aliasUsername = alias ? ( const aliasUsername = alias ? (
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text> <Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
) : null; ) : null;
const time = moment(ts).format(timeFormat); const time = moment(ts).format(timeFormat);
const onUserPress = () => navToRoomInfo(navParam); const onUserPress = () => navToRoomInfo(navParam);
const isDisabled = author._id === user.id; const isDisabled = author!._id === user.id;
const textContent = ( const textContent = (
<> <>

View File

@ -13,6 +13,7 @@ import { fileDownload } from '../../utils/fileDownload';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import { LISTENER } from '../Toast'; import { LISTENER } from '../Toast';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { IAttachment } from '../../definitions/IAttachment';
import RCActivityIndicator from '../ActivityIndicator'; import RCActivityIndicator from '../ActivityIndicator';
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
@ -30,14 +31,7 @@ const styles = StyleSheet.create({
}); });
interface IMessageVideo { interface IMessageVideo {
file: { file: IAttachment;
title: string;
title_link: string;
type: string;
video_type: string;
video_url: string;
description: string;
};
showAttachment: Function; showAttachment: Function;
getCustomEmoji: Function; getCustomEmoji: Function;
theme: string; theme: string;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Keyboard } from 'react-native'; import { Keyboard, ViewStyle } from 'react-native';
import Message from './Message'; import Message from './Message';
import MessageContext from './Context'; import MessageContext from './Context';
@ -17,53 +17,55 @@ interface IMessageContainerProps {
username: string; username: string;
token: string; token: string;
}; };
rid: string; msg?: string;
rid?: string;
timeFormat: string; timeFormat: string;
style: any; style?: ViewStyle;
archived: boolean; archived?: boolean;
broadcast: boolean; broadcast?: boolean;
previousItem: { previousItem?: {
ts: any; ts: any;
u: any; u: any;
groupable: any; groupable: any;
id: any; id: string;
tmid: any; tmid: string;
status: any; status: any;
}; };
isHeader: boolean;
baseUrl: string; baseUrl: string;
Message_GroupingPeriod: number; Message_GroupingPeriod?: number;
isReadReceiptEnabled: boolean; isReadReceiptEnabled?: boolean;
isThreadRoom: boolean; isThreadRoom: boolean;
useRealName: boolean; useRealName: boolean;
autoTranslateRoom: boolean; autoTranslateRoom?: boolean;
autoTranslateLanguage: string; autoTranslateLanguage?: string;
status: number; status?: number;
isIgnored: boolean; isIgnored?: boolean;
highlighted: boolean; highlighted?: boolean;
getCustomEmoji(): void; getCustomEmoji(name: string): void;
onLongPress: Function; onLongPress?: Function;
onReactionPress: Function; onReactionPress?: Function;
onEncryptedPress: Function; onEncryptedPress?: Function;
onDiscussionPress: Function; onDiscussionPress?: Function;
onThreadPress: Function; onThreadPress?: Function;
errorActionsShow: Function; errorActionsShow?: Function;
replyBroadcast: Function; replyBroadcast?: Function;
reactionInit: Function; reactionInit?: Function;
fetchThreadName: Function; fetchThreadName?: Function;
showAttachment: Function; showAttachment?: Function;
onReactionLongPress: Function; onReactionLongPress?: Function;
navToRoomInfo: Function; navToRoomInfo?: Function;
callJitsi: Function; callJitsi?: Function;
blockAction: Function; blockAction?: Function;
onAnswerButtonPress: Function; onAnswerButtonPress?: Function;
theme: string; theme: string;
threadBadgeColor: string; threadBadgeColor?: string;
toggleFollowThread: Function; toggleFollowThread?: Function;
jumpToMessage: Function; jumpToMessage?: Function;
onPress: Function; onPress: Function;
} }
class MessageContainer extends React.Component<IMessageContainerProps, any> { class MessageContainer extends React.Component<IMessageContainerProps> {
static defaultProps = { static defaultProps = {
getCustomEmoji: () => {}, getCustomEmoji: () => {},
onLongPress: () => {}, onLongPress: () => {},
@ -224,7 +226,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
previousItem.ts.toDateString() === item.ts.toDateString() && previousItem.ts.toDateString() === item.ts.toDateString() &&
previousItem.u.username === item.u.username && previousItem.u.username === item.u.username &&
!(previousItem.groupable === false || item.groupable === false || broadcast === true) && !(previousItem.groupable === false || item.groupable === false || broadcast === true) &&
item.ts - previousItem.ts < Message_GroupingPeriod * 1000 && item.ts - previousItem.ts < Message_GroupingPeriod! * 1000 &&
previousItem.tmid === item.tmid previousItem.tmid === item.tmid
) { ) {
return false; return false;
@ -299,7 +301,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
const { item, theme, jumpToMessage } = this.props; const { item, theme, jumpToMessage } = this.props;
const isMessageLink = item?.attachments?.findIndex((att: any) => att?.message_link === link) !== -1; const isMessageLink = item?.attachments?.findIndex((att: any) => att?.message_link === link) !== -1;
if (isMessageLink) { if (isMessageLink) {
return jumpToMessage(link); return jumpToMessage!(link);
} }
openLink(link, theme); openLink(link, theme);
}; };
@ -365,7 +367,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
// "autoTranslateRoom" and "autoTranslateLanguage" are properties from the subscription // "autoTranslateRoom" and "autoTranslateLanguage" are properties from the subscription
// "autoTranslateMessage" is a toggle between "View Original" and "Translate" state // "autoTranslateMessage" is a toggle between "View Original" and "Translate" state
if (autoTranslateRoom && autoTranslateMessage) { if (autoTranslateRoom && autoTranslateMessage) {
message = getMessageTranslation(item, autoTranslateLanguage) || message; message = getMessageTranslation(item, autoTranslateLanguage!) || message;
} }
return ( return (
@ -393,7 +395,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
id={id} id={id}
msg={message} msg={message}
md={md} md={md}
rid={rid} rid={rid!}
author={u} author={u}
ts={ts} ts={ts}
type={t} type={t}
@ -407,10 +409,10 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
emoji={emoji} emoji={emoji}
timeFormat={timeFormat} timeFormat={timeFormat}
style={style} style={style}
archived={archived} archived={archived!}
broadcast={broadcast} broadcast={broadcast!}
useRealName={useRealName} useRealName={useRealName}
isReadReceiptEnabled={isReadReceiptEnabled} isReadReceiptEnabled={isReadReceiptEnabled!}
unread={unread} unread={unread}
role={role} role={role}
drid={drid} drid={drid}
@ -420,10 +422,10 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
tcount={tcount} tcount={tcount}
tlm={tlm} tlm={tlm}
tmsg={tmsg} tmsg={tmsg}
fetchThreadName={fetchThreadName} fetchThreadName={fetchThreadName!}
mentions={mentions} mentions={mentions}
channels={channels} channels={channels}
isIgnored={this.isIgnored} isIgnored={this.isIgnored!}
isEdited={editedBy && !!editedBy.username} isEdited={editedBy && !!editedBy.username}
isHeader={this.isHeader} isHeader={this.isHeader}
isThreadReply={this.isThreadReply} isThreadReply={this.isThreadReply}
@ -433,13 +435,13 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
isTemp={this.isTemp} isTemp={this.isTemp}
isEncrypted={this.isEncrypted} isEncrypted={this.isEncrypted}
hasError={this.hasError} hasError={this.hasError}
showAttachment={showAttachment} showAttachment={showAttachment!}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
navToRoomInfo={navToRoomInfo} navToRoomInfo={navToRoomInfo!}
callJitsi={callJitsi} callJitsi={callJitsi!}
blockAction={blockAction} blockAction={blockAction!}
theme={theme} theme={theme}
highlighted={highlighted} highlighted={highlighted!}
/> />
</MessageContext.Provider> </MessageContext.Provider>
); );

View File

@ -51,12 +51,13 @@ export interface IMessageCallButton {
} }
export interface IUser { export interface IUser {
_id: string; id: string;
username: string; username: string;
token: string;
name: string; name: string;
} }
export type UserMention = Pick<IUser, '_id' | 'username' | 'name'>; export type UserMention = Pick<IUser, 'id' | 'username' | 'name'>;
export interface IMessageContent { export interface IMessageContent {
_id: string; _id: string;
@ -84,7 +85,7 @@ export interface IMessageContent {
export interface IMessageDiscussion { export interface IMessageDiscussion {
msg: string; msg: string;
dcount: number; dcount: number;
dlm: string; dlm: Date;
theme: string; theme: string;
} }

View File

@ -26,7 +26,9 @@ export const SYSTEM_MESSAGES = [
'au', 'au',
'ru', 'ru',
'ul', 'ul',
'ult',
'uj', 'uj',
'ujt',
'ut', 'ut',
'rm', 'rm',
'user-muted', 'user-muted',
@ -50,8 +52,10 @@ export const SYSTEM_MESSAGE_TYPES = {
MESSAGE_PINNED: 'message_pinned', MESSAGE_PINNED: 'message_pinned',
MESSAGE_SNIPPETED: 'message_snippeted', MESSAGE_SNIPPETED: 'message_snippeted',
USER_JOINED_CHANNEL: 'uj', USER_JOINED_CHANNEL: 'uj',
USER_JOINED_TEAM: 'ujt',
USER_JOINED_DISCUSSION: 'ut', USER_JOINED_DISCUSSION: 'ut',
USER_LEFT_CHANNEL: 'ul' USER_LEFT_CHANNEL: 'ul',
USER_LEFT_TEAM: 'ult'
}; };
export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [ export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
@ -59,8 +63,10 @@ export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
SYSTEM_MESSAGE_TYPES.MESSAGE_PINNED, SYSTEM_MESSAGE_TYPES.MESSAGE_PINNED,
SYSTEM_MESSAGE_TYPES.MESSAGE_SNIPPETED, SYSTEM_MESSAGE_TYPES.MESSAGE_SNIPPETED,
SYSTEM_MESSAGE_TYPES.USER_JOINED_CHANNEL, SYSTEM_MESSAGE_TYPES.USER_JOINED_CHANNEL,
SYSTEM_MESSAGE_TYPES.USER_JOINED_TEAM,
SYSTEM_MESSAGE_TYPES.USER_JOINED_DISCUSSION, SYSTEM_MESSAGE_TYPES.USER_JOINED_DISCUSSION,
SYSTEM_MESSAGE_TYPES.USER_LEFT_CHANNEL SYSTEM_MESSAGE_TYPES.USER_LEFT_CHANNEL,
SYSTEM_MESSAGE_TYPES.USER_LEFT_TEAM
]; ];
type TInfoMessage = { type TInfoMessage = {
@ -77,6 +83,9 @@ export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage) => {
if (type === 'uj') { if (type === 'uj') {
return I18n.t('Has_joined_the_channel'); return I18n.t('Has_joined_the_channel');
} }
if (type === 'ujt') {
return I18n.t('Has_joined_the_team');
}
if (type === 'ut') { if (type === 'ut') {
return I18n.t('Has_joined_the_conversation'); return I18n.t('Has_joined_the_conversation');
} }
@ -92,6 +101,9 @@ export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage) => {
if (type === 'ul') { if (type === 'ul') {
return I18n.t('Has_left_the_channel'); return I18n.t('Has_left_the_channel');
} }
if (type === 'ult') {
return I18n.t('Has_left_the_team');
}
if (type === 'ru') { if (type === 'ru') {
return I18n.t('User_removed_by', { userRemoved: msg, userBy: username }); return I18n.t('User_removed_by', { userRemoved: msg, userBy: username });
} }

View File

@ -1,4 +1,5 @@
export interface IAttachment { export interface IAttachment {
ts: Date;
title: string; title: string;
type: string; type: string;
description: string; description: string;
@ -7,4 +8,18 @@ export interface IAttachment {
image_type?: string; image_type?: string;
video_url?: string; video_url?: string;
video_type?: string; video_type?: string;
title_link_download?: boolean;
fields?: IAttachment[];
image_dimensions?: { width?: number; height?: number };
image_preview?: string;
image_size?: number;
author_name?: string;
author_icon?: string;
message_link?: string;
text?: string;
short?: boolean;
value?: string;
author_link?: string;
color?: string;
thumb_url?: string;
} }

View File

@ -0,0 +1,6 @@
export interface ICommand {
event: {
input: string;
modifierFlags: number;
};
}

View File

@ -0,0 +1,10 @@
import Model from '@nozbe/watermelondb/Model';
export interface ICustomEmoji {
name?: string;
aliases?: string;
extension: string;
_updatedAt: Date;
}
export type TCustomEmojiModel = ICustomEmoji & Model;

View File

@ -0,0 +1,10 @@
import Model from '@nozbe/watermelondb/Model';
export interface IFrequentlyUsedEmoji {
content?: string;
extension?: string;
isCustom: boolean;
count: number;
}
export type TFrequentlyUsedEmoji = IFrequentlyUsedEmoji & Model;

View File

@ -0,0 +1,18 @@
import Model from '@nozbe/watermelondb/Model';
export interface ILoggedUser {
id: string;
token: string;
username: string;
name: string;
language?: string;
status: string;
statusText?: string;
roles: string[];
avatarETag?: string;
showMessageInMainThread: boolean;
isFromWebView: boolean;
enableMessageParserEarlyAdoption?: boolean;
}
export type TLoggedUser = ILoggedUser & Model;

View File

@ -0,0 +1,6 @@
export interface IMention {
_id: string;
name: string;
username: string;
type: string;
}

View File

@ -1,3 +1,95 @@
export interface IMessage { import Model from '@nozbe/watermelondb/Model';
msg: string; import { MarkdownAST } from '@rocket.chat/message-parser';
import { IAttachment } from './IAttachment';
import { IReaction } from './IReaction';
import { SubscriptionType } from './ISubscription';
export interface IUserMessage {
_id: string;
username?: string;
name?: string;
} }
export interface IUserMention extends IUserMessage {
type: string;
}
export interface IUserChannel {
[index: number]: string | number;
name: string;
_id: string;
}
export interface IEditedBy {
_id: string;
username: string;
}
export type TOnLinkPress = (link: string) => void;
export interface ITranslations {
_id: string;
language: string;
value: string;
}
export interface ILastMessage {
_id: string;
rid: string;
tshow: boolean;
tmid: string;
msg: string;
ts: Date;
u: IUserMessage;
_updatedAt: Date;
urls: string[];
mentions: IUserMention[];
channels: IUserChannel[];
md: MarkdownAST;
attachments: IAttachment[];
reactions: IReaction[];
unread: boolean;
status: boolean;
}
export interface IMessage {
msg?: string;
t?: SubscriptionType;
ts: Date;
u: IUserMessage;
alias: string;
parseUrls: boolean;
groupable?: boolean;
avatar?: string;
emoji?: string;
attachments?: IAttachment[];
urls?: string[];
_updatedAt: Date;
status?: number;
pinned?: boolean;
starred?: boolean;
editedBy?: IEditedBy;
reactions?: IReaction[];
role?: string;
drid?: string;
dcount?: number;
dlm?: Date;
tmid?: string;
tcount?: number;
tlm?: Date;
replies?: string[];
mentions?: IUserMention[];
channels?: IUserChannel[];
unread?: boolean;
autoTranslate?: boolean;
translations?: ITranslations[];
tmsg?: string;
blocks?: any;
e2e?: string;
tshow?: boolean;
md?: MarkdownAST;
subscription: { id: string };
}
export type TMessageModel = IMessage & Model;

View File

@ -0,0 +1,13 @@
export interface INotification {
message: string;
style: string;
ejson: string;
collapse_key: string;
notId: string;
msgcnt: string;
title: string;
from: string;
image: string;
soundname: string;
getData: () => INotification;
}

View File

@ -0,0 +1,9 @@
import Model from '@nozbe/watermelondb/Model';
export interface IPermission {
id: string;
roles: string[];
_updatedAt: Date;
}
export type TPermissionModel = IPermission & Model;

View File

@ -0,0 +1,5 @@
export interface IReaction {
_id: string;
emoji: string;
usernames: string[];
}

View File

@ -1,4 +0,0 @@
export interface IRocketChatRecord {
id: string;
updatedAt: Date;
}

8
app/definitions/IRole.ts Normal file
View File

@ -0,0 +1,8 @@
import Model from '@nozbe/watermelondb/Model';
export interface IRole {
id: string;
description?: string;
}
export type TRoleModel = IRole & Model;

View File

@ -1,27 +1,27 @@
import { IRocketChatRecord } from './IRocketChatRecord'; import Model from '@nozbe/watermelondb/Model';
export enum RoomType { import { IServedBy } from './IServedBy';
GROUP = 'p', import { SubscriptionType } from './ISubscription';
DIRECT = 'd',
CHANNEL = 'c',
OMNICHANNEL = 'l',
THREAD = 'thread'
}
export interface IRoom extends IRocketChatRecord { export interface IRoom {
id: string;
rid: string; rid: string;
t: RoomType; prid: string;
t: SubscriptionType;
name: string; name: string;
fname: string; teamMain: boolean;
prid?: string; alert?: boolean;
tmid?: string; customFields: string[];
topic?: string; broadcast: boolean;
teamMain?: boolean; encrypted: boolean;
teamId?: string; ro: boolean;
encrypted?: boolean; v?: string[];
visitor?: boolean; servedBy?: IServedBy;
autoTranslateLanguage?: boolean; departmentId?: string;
autoTranslate?: boolean; livechatData?: any;
observe?: Function; tags?: string[];
usedCannedResponse: string; e2eKeyId?: string;
avatarETag?: string;
} }
export type TRoomModel = IRoom & Model;

View File

@ -0,0 +1,5 @@
export interface IServedBy {
_id: string;
username: string;
ts: Date;
}

View File

@ -1,3 +1,5 @@
import Model from '@nozbe/watermelondb/Model';
export interface IServer { export interface IServer {
name: string; name: string;
iconURL: string; iconURL: string;
@ -8,9 +10,11 @@ export interface IServer {
version: string; version: string;
lastLocalAuthenticatedSession: Date; lastLocalAuthenticatedSession: Date;
autoLock: boolean; autoLock: boolean;
autoLockTime: number | null; autoLockTime?: number;
biometry: boolean | null; biometry?: boolean;
uniqueID: string; uniqueID: string;
enterpriseModules: string; enterpriseModules: string;
E2E_Enable: boolean; E2E_Enable: boolean;
} }
export type TServerModel = IServer & Model;

View File

@ -0,0 +1,10 @@
import Model from '@nozbe/watermelondb/Model';
export interface IServerHistory {
id: string;
url: string;
username: string;
updatedAt: Date;
}
export type TServerHistory = IServerHistory & Model;

View File

@ -0,0 +1,12 @@
import Model from '@nozbe/watermelondb/Model';
export interface ISettings {
id: string;
valueAsString?: string;
valueAsBoolean?: boolean;
valueAsNumber?: number;
valueAsArray?: string[];
_updatedAt?: Date;
}
export type TSettingsModel = ISettings & Model;

View File

@ -0,0 +1,12 @@
import Model from '@nozbe/watermelondb/Model';
export interface ISlashCommand {
id: string;
params?: string;
description?: string;
clientOnly?: boolean;
providesPreview?: boolean;
appId?: string;
}
export type TSlashCommandModel = ISlashCommand & Model;

View File

@ -0,0 +1,91 @@
import Model from '@nozbe/watermelondb/Model';
import Relation from '@nozbe/watermelondb/Relation';
import { ILastMessage, TMessageModel } from './IMessage';
import { IServedBy } from './IServedBy';
import { TThreadModel } from './IThread';
import { TThreadMessageModel } from './IThreadMessage';
import { TUploadModel } from './IUpload';
export enum SubscriptionType {
GROUP = 'p',
DIRECT = 'd',
CHANNEL = 'c',
OMNICHANNEL = 'l',
THREAD = 'thread'
}
export interface IVisitor {
_id: string;
username: string;
token: string;
status: string;
lastMessageTs: Date;
}
export interface ISubscription {
_id: string; // _id belongs watermelonDB
id: string; // id from server
f: boolean;
t: SubscriptionType;
ts: Date;
ls: Date;
name: string;
fname?: string;
rid: string; // the same as id
open: boolean;
alert: boolean;
roles?: string[];
unread: number;
userMentions: number;
groupMentions: number;
tunread?: string[];
tunreadUser?: string[];
tunreadGroup?: string[];
roomUpdatedAt: Date;
ro: boolean;
lastOpen?: Date;
description?: string;
announcement?: string;
bannerClosed?: boolean;
topic?: string;
blocked?: boolean;
blocker?: boolean;
reactWhenReadOnly?: boolean;
archived: boolean;
joinCodeRequired?: boolean;
muted?: string[];
ignored?: string[];
broadcast?: boolean;
prid?: string;
draftMessage?: string;
lastThreadSync?: Date;
jitsiTimeout?: number;
autoTranslate?: boolean;
autoTranslateLanguage: string;
lastMessage?: ILastMessage;
hideUnreadStatus?: boolean;
sysMes?: string[] | boolean;
uids?: string[];
usernames?: string[];
visitor?: IVisitor;
departmentId?: string;
servedBy?: IServedBy;
livechatData?: any;
tags?: string[];
E2EKey?: string;
encrypted?: boolean;
e2eKeyId?: string;
avatarETag?: string;
teamId?: string;
teamMain?: boolean;
search?: boolean;
username?: string;
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
messages: Relation<TMessageModel>;
threads: Relation<TThreadModel>;
threadMessages: Relation<TThreadMessageModel>;
uploads: Relation<TUploadModel>;
}
export type TSubscriptionModel = ISubscription & Model;

View File

@ -0,0 +1,8 @@
export type TThemeMode = 'automatic' | 'light' | 'dark';
export type TDarkLevel = 'black' | 'dark';
export interface IThemePreference {
currentTheme: TThemeMode;
darkLevel: TDarkLevel;
}

View File

@ -0,0 +1,78 @@
import Model from '@nozbe/watermelondb/Model';
import { MarkdownAST } from '@rocket.chat/message-parser';
import { IAttachment } from './IAttachment';
import { IEditedBy, IUserChannel, IUserMention, IUserMessage } from './IMessage';
import { IReaction } from './IReaction';
import { SubscriptionType } from './ISubscription';
export interface IUrl {
title: string;
description: string;
image: string;
url: string;
}
interface IFileThread {
_id: string;
name: string;
type: string;
}
export interface IThreadResult {
_id: string;
rid: string;
ts: string;
msg: string;
file?: IFileThread;
files?: IFileThread[];
groupable?: boolean;
attachments?: IAttachment[];
md?: MarkdownAST;
u: IUserMessage;
_updatedAt: string;
urls: IUrl[];
mentions: IUserMention[];
channels: IUserChannel[];
replies: string[];
tcount: number;
tlm: string;
}
export interface IThread {
id: string;
msg?: string;
t?: SubscriptionType;
rid?: string;
_updatedAt?: Date;
ts?: Date;
u?: IUserMessage;
alias?: string;
parseUrls?: boolean;
groupable?: boolean;
avatar?: string;
emoji?: string;
attachments?: IAttachment[];
urls?: IUrl[];
status?: number;
pinned?: boolean;
starred?: boolean;
editedBy?: IEditedBy;
reactions?: IReaction[];
role?: string;
drid?: string;
dcount?: number;
dlm?: number;
tmid?: string;
tcount: number | string;
tlm?: string;
replies?: string[];
mentions?: IUserMention[];
channels?: IUserChannel[];
unread?: boolean;
autoTranslate?: boolean;
translations?: any;
e2e?: string;
}
export type TThreadModel = IThread & Model;

View File

@ -0,0 +1,44 @@
import Model from '@nozbe/watermelondb/Model';
import { IAttachment } from './IAttachment';
import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage } from './IMessage';
import { IReaction } from './IReaction';
import { SubscriptionType } from './ISubscription';
export interface IThreadMessage {
msg?: string;
t?: SubscriptionType;
rid: string;
ts: Date;
u: IUserMessage;
alias?: string;
parseUrls?: boolean;
groupable?: boolean;
avatar?: string;
emoji?: string;
attachments?: IAttachment[];
urls?: string[];
_updatedAt?: Date;
status?: number;
pinned?: boolean;
starred?: boolean;
editedBy?: IEditedBy;
reactions?: IReaction[];
role?: string;
drid?: string;
dcount?: number;
dlm?: Date;
tmid?: string;
tcount?: number;
tlm?: Date;
replies?: string[];
mentions?: IUserMention[];
channels?: IUserChannel[];
unread?: boolean;
autoTranslate?: boolean;
translations?: ITranslations[];
e2e?: string;
subscription?: { id: string };
}
export type TThreadMessageModel = IThreadMessage & Model;

View File

@ -0,0 +1,16 @@
import Model from '@nozbe/watermelondb/Model';
export interface IUpload {
id: string;
path?: string;
name?: string;
description?: string;
size: number;
type?: string;
store?: string;
progress: number;
error: boolean;
subscription: { id: string };
}
export type TUploadModel = IUpload & Model;

6
app/definitions/IUrl.ts Normal file
View File

@ -0,0 +1,6 @@
export interface IUrl {
title: string;
description: string;
image: string;
url: string;
}

10
app/definitions/IUser.ts Normal file
View File

@ -0,0 +1,10 @@
import Model from '@nozbe/watermelondb/Model';
export interface IUser {
_id: string;
name?: string;
username: string;
avatarETag?: string;
}
export type TUserModel = IUser & Model;

19
app/definitions/index.ts Normal file
View File

@ -0,0 +1,19 @@
import { RouteProp } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { Dispatch } from 'redux';
export * from './IAttachment';
export * from './IMessage';
export * from './INotification';
export * from './IRoom';
export * from './IServer';
export * from './ISubscription';
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
navigation: StackNavigationProp<T, S>;
route: RouteProp<T, S>;
dispatch: Dispatch;
theme: string;
}
export * from './redux';

View File

@ -1,10 +1,21 @@
import { NavigatorScreenParams } from '@react-navigation/core'; import { NavigatorScreenParams } from '@react-navigation/core';
import { StackNavigationOptions } from '@react-navigation/stack';
import { IRoom } from './definitions/IRoom'; import { ISubscription } from './ISubscription';
import { IServer } from './definitions/IServer'; import { IServer } from './IServer';
import { IAttachment } from './definitions/IAttachment'; import { IAttachment } from './IAttachment';
import { MasterDetailInsideStackParamList } from './stacks/MasterDetailStack/types'; import { MasterDetailInsideStackParamList } from '../stacks/MasterDetailStack/types';
import { OutsideParamList, InsideStackParamList } from './stacks/types'; import { OutsideParamList, InsideStackParamList } from '../stacks/types';
interface INavigationProps {
route?: any;
navigation?: any;
isMasterDetail?: boolean;
}
export type TNavigationOptions = {
navigationOptions?(props: INavigationProps): StackNavigationOptions;
};
export type SetUsernameStackParamList = { export type SetUsernameStackParamList = {
SetUsernameView: { SetUsernameView: {
@ -28,7 +39,7 @@ export type ShareInsideStackParamList = {
isShareExtension: boolean; isShareExtension: boolean;
serverInfo: IServer; serverInfo: IServer;
text: string; text: string;
room: IRoom; room: ISubscription;
thread: any; // TODO: Change thread: any; // TODO: Change
}; };
SelectServerView: undefined; SelectServerView: undefined;

View File

@ -0,0 +1,31 @@
import { TActionSelectedUsers } from '../../actions/selectedUsers';
import { TActionActiveUsers } from '../../actions/activeUsers';
// REDUCERS
import { IActiveUsers } from '../../reducers/activeUsers';
import { ISelectedUsers } from '../../reducers/selectedUsers';
export interface IApplicationState {
settings: any;
login: any;
meteor: any;
server: any;
selectedUsers: ISelectedUsers;
createChannel: any;
app: any;
room: any;
rooms: any;
sortPreferences: any;
share: any;
customEmojis: any;
activeUsers: IActiveUsers;
usersTyping: any;
inviteLinks: any;
createDiscussion: any;
inquiry: any;
enterpriseModules: any;
encryption: any;
permissions: any;
roles: any;
}
export type TApplicationActions = TActionActiveUsers & TActionSelectedUsers;

View File

@ -2,6 +2,8 @@ import React from 'react';
import { Dimensions } from 'react-native'; import { Dimensions } from 'react-native';
import hoistNonReactStatics from 'hoist-non-react-statics'; import hoistNonReactStatics from 'hoist-non-react-statics';
import { TNavigationOptions } from './definitions/navigationTypes';
export interface IDimensionsContextProps { export interface IDimensionsContextProps {
width: number; width: number;
height?: number; height?: number;
@ -22,10 +24,11 @@ export interface IDimensionsContextProps {
export const DimensionsContext = React.createContext<Partial<IDimensionsContextProps>>(Dimensions.get('window')); export const DimensionsContext = React.createContext<Partial<IDimensionsContextProps>>(Dimensions.get('window'));
export function withDimensions(Component: any): any { export function withDimensions<T extends object>(Component: React.ComponentType<T> & TNavigationOptions): typeof Component {
const DimensionsComponent = (props: any) => ( const DimensionsComponent = (props: T) => (
<DimensionsContext.Consumer>{contexts => <Component {...props} {...contexts} />}</DimensionsContext.Consumer> <DimensionsContext.Consumer>{contexts => <Component {...props} {...contexts} />}</DimensionsContext.Consumer>
); );
hoistNonReactStatics(DimensionsComponent, Component); hoistNonReactStatics(DimensionsComponent, Component);
return DimensionsComponent; return DimensionsComponent;
} }

View File

@ -13,3 +13,4 @@ declare module 'react-native-mime-types';
declare module 'react-native-restart'; declare module 'react-native-restart';
declare module 'react-native-prompt-android'; declare module 'react-native-prompt-android';
declare module 'react-native-jitsi-meet'; declare module 'react-native-jitsi-meet';
declare module 'rn-root-view';

View File

@ -249,8 +249,10 @@
"Full_table": "Click to see full table", "Full_table": "Click to see full table",
"Generate_New_Link": "Generate New Link", "Generate_New_Link": "Generate New Link",
"Has_joined_the_channel": "has joined the channel", "Has_joined_the_channel": "has joined the channel",
"Has_joined_the_team": "has joined the team",
"Has_joined_the_conversation": "has joined the conversation", "Has_joined_the_conversation": "has joined the conversation",
"Has_left_the_channel": "has left the channel", "Has_left_the_channel": "has left the channel",
"Has_left_the_team": "has left the team",
"Hide_System_Messages": "Hide System Messages", "Hide_System_Messages": "Hide System Messages",
"Hide_type_messages": "Hide \"{{type}}\" messages", "Hide_type_messages": "Hide \"{{type}}\" messages",
"How_It_Works": "How It Works", "How_It_Works": "How It Works",

View File

@ -30,6 +30,8 @@ import InAppNotification from './containers/InAppNotification';
import { ActionSheetProvider } from './containers/ActionSheet'; import { ActionSheetProvider } from './containers/ActionSheet';
import debounce from './utils/debounce'; import debounce from './utils/debounce';
import { isFDroidBuild } from './constants/environment'; import { isFDroidBuild } from './constants/environment';
import { IThemePreference } from './definitions/ITheme';
import { ICommand } from './definitions/ICommand';
RNScreens.enableScreens(); RNScreens.enableScreens();
@ -42,10 +44,7 @@ interface IDimensions {
interface IState { interface IState {
theme: string; theme: string;
themePreferences: { themePreferences: IThemePreference;
currentTheme: 'automatic' | 'light';
darkLevel: string;
};
width: number; width: number;
height: number; height: number;
scale: number; scale: number;
@ -175,7 +174,7 @@ export default class Root extends React.Component<{}, IState> {
setTheme = (newTheme = {}) => { setTheme = (newTheme = {}) => {
// change theme state // change theme state
this.setState( this.setState(
prevState => newThemeState(prevState, newTheme), prevState => newThemeState(prevState, newTheme as IThemePreference),
() => { () => {
const { themePreferences } = this.state; const { themePreferences } = this.state;
// subscribe to Appearance changes // subscribe to Appearance changes
@ -191,7 +190,7 @@ export default class Root extends React.Component<{}, IState> {
initTablet = () => { initTablet = () => {
const { width } = this.state; const { width } = this.state;
this.setMasterDetail(width); this.setMasterDetail(width);
this.onKeyCommands = KeyCommandsEmitter.addListener('onKeyCommand', (command: unknown) => { this.onKeyCommands = KeyCommandsEmitter.addListener('onKeyCommand', (command: ICommand) => {
EventEmitter.emit(KEY_COMMAND, { event: command }); EventEmitter.emit(KEY_COMMAND, { event: command });
}); });
}; };

View File

@ -835,17 +835,21 @@ const RocketChat = {
// RC 3.13.0 // RC 3.13.0
return this.post('teams.removeRoom', { roomId, teamId }); return this.post('teams.removeRoom', { roomId, teamId });
}, },
leaveTeam({ teamName, rooms }) { leaveTeam({ teamId, rooms }) {
// RC 3.13.0 // RC 3.13.0
return this.post('teams.leave', { teamName, rooms }); return this.post('teams.leave', {
teamId,
// RC 4.2.0
...(rooms?.length && { rooms })
});
}, },
removeTeamMember({ teamId, teamName, userId, rooms }) { removeTeamMember({ teamId, userId, rooms }) {
// RC 3.13.0 // RC 3.13.0
return this.post('teams.removeMember', { return this.post('teams.removeMember', {
teamId, teamId,
teamName,
userId, userId,
rooms // RC 4.2.0
...(rooms?.length && { rooms })
}); });
}, },
updateTeamRoom({ roomId, isDefault }) { updateTeamRoom({ roomId, isDefault }) {

View File

@ -7,11 +7,12 @@ const MMKV = new MMKVStorage.Loader()
.initialize(); .initialize();
class UserPreferences { class UserPreferences {
private mmkv: MMKVStorage.API;
constructor() { constructor() {
this.mmkv = MMKV; this.mmkv = MMKV;
} }
async getStringAsync(key) { async getStringAsync(key: string) {
try { try {
const value = await this.mmkv.getStringAsync(key); const value = await this.mmkv.getStringAsync(key);
return value; return value;
@ -20,11 +21,11 @@ class UserPreferences {
} }
} }
setStringAsync(key, value) { setStringAsync(key: string, value: string) {
return this.mmkv.setStringAsync(key, value); return this.mmkv.setStringAsync(key, value);
} }
async getBoolAsync(key) { async getBoolAsync(key: string) {
try { try {
const value = await this.mmkv.getBoolAsync(key); const value = await this.mmkv.getBoolAsync(key);
return value; return value;
@ -33,11 +34,11 @@ class UserPreferences {
} }
} }
setBoolAsync(key, value) { setBoolAsync(key: string, value: boolean) {
return this.mmkv.setBoolAsync(key, value); return this.mmkv.setBoolAsync(key, value);
} }
async getMapAsync(key) { async getMapAsync(key: string) {
try { try {
const value = await this.mmkv.getMapAsync(key); const value = await this.mmkv.getMapAsync(key);
return value; return value;
@ -46,11 +47,11 @@ class UserPreferences {
} }
} }
setMapAsync(key, value) { setMapAsync(key: string, value: object) {
return this.mmkv.setMapAsync(key, value); return this.mmkv.setMapAsync(key, value);
} }
removeItem(key) { removeItem(key: string) {
return this.mmkv.removeItem(key); return this.mmkv.removeItem(key);
} }
} }

View File

@ -1,50 +0,0 @@
import EJSON from 'ejson';
import store from '../../lib/createStore';
import { deepLinkingOpen } from '../../actions/deepLinking';
import { isFDroidBuild } from '../../constants/environment';
import PushNotification from './push';
export const onNotification = notification => {
if (notification) {
const data = notification.getData();
if (data) {
try {
const { rid, name, sender, type, host, messageType, messageId } = EJSON.parse(data.ejson);
const types = {
c: 'channel',
d: 'direct',
p: 'group',
l: 'channels'
};
let roomName = type === 'd' ? sender.username : name;
if (type === 'l') {
roomName = sender.name;
}
const params = {
host,
rid,
messageId,
path: `${types[type]}/${roomName}`,
isCall: messageType === 'jitsi_call_started'
};
store.dispatch(deepLinkingOpen(params));
} catch (e) {
console.warn(e);
}
}
}
};
export const getDeviceToken = () => PushNotification.getDeviceToken();
export const setBadgeCount = count => PushNotification.setBadgeCount(count);
export const initializePushNotifications = () => {
if (!isFDroidBuild) {
setBadgeCount();
return PushNotification.configure({
onNotification
});
}
};

View File

@ -0,0 +1,58 @@
import EJSON from 'ejson';
import store from '../../lib/createStore';
import { deepLinkingOpen } from '../../actions/deepLinking';
import { isFDroidBuild } from '../../constants/environment';
import PushNotification from './push';
import { INotification, SubscriptionType } from '../../definitions';
interface IEjson {
rid: string;
name: string;
sender: { username: string; name: string };
type: string;
host: string;
messageType: string;
messageId: string;
}
export const onNotification = (push: INotification): void => {
if (push) {
try {
const notification = push?.getData();
const { rid, name, sender, type, host, messageType, messageId }: IEjson = EJSON.parse(notification.ejson);
const types: Record<string, string> = {
c: 'channel',
d: 'direct',
p: 'group',
l: 'channels'
};
let roomName = type === SubscriptionType.DIRECT ? sender.username : name;
if (type === SubscriptionType.OMNICHANNEL) {
roomName = sender.name;
}
const params = {
host,
rid,
messageId,
path: `${types[type]}/${roomName}`,
isCall: messageType === 'jitsi_call_started'
};
// TODO REDUX MIGRATION TO TS
store.dispatch(deepLinkingOpen(params));
} catch (e) {
console.warn(e);
}
}
};
export const getDeviceToken = (): string => PushNotification.getDeviceToken();
export const setBadgeCount = (count?: number): void => PushNotification.setBadgeCount(count);
export const initializePushNotifications = (): Promise<INotification> | undefined => {
if (!isFDroidBuild) {
setBadgeCount();
return PushNotification.configure(onNotification);
}
};

View File

@ -1,32 +0,0 @@
import { NotificationsAndroid, PendingNotifications } from 'react-native-notifications';
class PushNotification {
constructor() {
this.onRegister = null;
this.onNotification = null;
this.deviceToken = null;
NotificationsAndroid.setRegistrationTokenUpdateListener(deviceToken => {
this.deviceToken = deviceToken;
});
NotificationsAndroid.setNotificationOpenedListener(notification => {
this.onNotification(notification);
});
}
getDeviceToken() {
return this.deviceToken;
}
setBadgeCount = () => {};
configure(params) {
this.onRegister = params.onRegister;
this.onNotification = params.onNotification;
NotificationsAndroid.refreshToken();
return PendingNotifications.getInitialNotification();
}
}
export default new PushNotification();

View File

@ -1,29 +1,25 @@
import NotificationsIOS, { NotificationAction, NotificationCategory } from 'react-native-notifications'; // @ts-ignore
// TODO BUMP LIB VERSION
import NotificationsIOS, { NotificationAction, NotificationCategory, Notification } from 'react-native-notifications';
import reduxStore from '../../lib/createStore'; import reduxStore from '../../lib/createStore';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { INotification } from '../../definitions/INotification';
const replyAction = new NotificationAction({
activationMode: 'background',
title: I18n.t('Reply'),
textInput: {
buttonTitle: I18n.t('Reply'),
placeholder: I18n.t('Type_message')
},
identifier: 'REPLY_ACTION'
});
class PushNotification { class PushNotification {
constructor() { onNotification: (notification: Notification) => void;
this.onRegister = null; deviceToken: string;
this.onNotification = null;
this.deviceToken = null;
NotificationsIOS.addEventListener('remoteNotificationsRegistered', deviceToken => { constructor() {
this.onNotification = () => {};
this.deviceToken = '';
NotificationsIOS.addEventListener('remoteNotificationsRegistered', (deviceToken: string) => {
this.deviceToken = deviceToken; this.deviceToken = deviceToken;
}); });
NotificationsIOS.addEventListener('notificationOpened', (notification, completion) => { NotificationsIOS.addEventListener('notificationOpened', (notification: Notification, completion: () => void) => {
// TODO REDUX MIGRATION TO TS
const { background } = reduxStore.getState().app; const { background } = reduxStore.getState().app;
if (background) { if (background) {
this.onNotification(notification); this.onNotification(notification);
@ -31,13 +27,22 @@ class PushNotification {
completion(); completion();
}); });
const actions = []; const actions = [
actions.push(
new NotificationCategory({ new NotificationCategory({
identifier: 'MESSAGE', identifier: 'MESSAGE',
actions: [replyAction] actions: [
new NotificationAction({
activationMode: 'background',
title: I18n.t('Reply'),
textInput: {
buttonTitle: I18n.t('Reply'),
placeholder: I18n.t('Type_message')
},
identifier: 'REPLY_ACTION'
}) })
); ]
})
];
NotificationsIOS.requestPermissions(actions); NotificationsIOS.requestPermissions(actions);
} }
@ -49,12 +54,9 @@ class PushNotification {
NotificationsIOS.setBadgesCount(count); NotificationsIOS.setBadgesCount(count);
}; };
async configure(params) { async configure(onNotification: (notification: INotification) => void) {
this.onRegister = params.onRegister; this.onNotification = onNotification;
this.onNotification = params.onNotification;
const initial = await NotificationsIOS.getInitialNotification(); const initial = await NotificationsIOS.getInitialNotification();
// NotificationsIOS.consumeBackgroundQueue();
return Promise.resolve(initial); return Promise.resolve(initial);
} }
} }

View File

@ -0,0 +1,36 @@
// @ts-ignore
// TODO BUMP LIB VERSION
import { NotificationsAndroid, PendingNotifications, Notification } from 'react-native-notifications';
import { INotification } from '../../definitions/INotification';
class PushNotification {
onNotification: (notification: Notification) => void;
deviceToken: string;
constructor() {
this.onNotification = () => {};
this.deviceToken = '';
NotificationsAndroid.setRegistrationTokenUpdateListener((deviceToken: string) => {
this.deviceToken = deviceToken;
});
NotificationsAndroid.setNotificationOpenedListener((notification: Notification) => {
this.onNotification(notification);
});
}
getDeviceToken() {
return this.deviceToken;
}
setBadgeCount = (_?: number) => {};
configure(onNotification: (notification: INotification) => void) {
this.onNotification = onNotification;
NotificationsAndroid.refreshToken();
return PendingNotifications.getInitialNotification();
}
}
export default new PushNotification();

View File

@ -5,7 +5,7 @@ import { RectButton } from 'react-native-gesture-handler';
import { isRTL } from '../../i18n'; import { isRTL } from '../../i18n';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { DISPLAY_MODE_CONDENSED } from '../../constants/constantDisplayMode'; import { DisplayMode } from '../../constants/constantDisplayMode';
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles'; import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
interface ILeftActions { interface ILeftActions {
@ -40,7 +40,7 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
reverse reverse
); );
const isCondensed = displayMode === DISPLAY_MODE_CONDENSED; const isCondensed = displayMode === DisplayMode.Condensed;
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null; const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
return ( return (
@ -87,7 +87,7 @@ export const RightActions = React.memo(
reverse reverse
); );
const isCondensed = displayMode === DISPLAY_MODE_CONDENSED; const isCondensed = displayMode === DisplayMode.Condensed;
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null; const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
return ( return (

View File

@ -3,7 +3,7 @@ import { View } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import { DISPLAY_MODE_CONDENSED, DISPLAY_MODE_EXPANDED } from '../../constants/constantDisplayMode'; import { DisplayMode } from '../../constants/constantDisplayMode';
import TypeIcon from './TypeIcon'; import TypeIcon from './TypeIcon';
import styles from './styles'; import styles from './styles';
@ -22,11 +22,11 @@ const IconOrAvatar = ({
}) => { }) => {
if (showAvatar) { if (showAvatar) {
return ( return (
<Avatar text={avatar} size={displayMode === DISPLAY_MODE_CONDENSED ? 36 : 48} type={type} style={styles.avatar} rid={rid} /> <Avatar text={avatar} size={displayMode === DisplayMode.Condensed ? 36 : 48} type={type} style={styles.avatar} rid={rid} />
); );
} }
if (displayMode === DISPLAY_MODE_EXPANDED && showLastMessage) { if (displayMode === DisplayMode.Expanded && showLastMessage) {
return ( return (
<View style={styles.typeIcon}> <View style={styles.typeIcon}>
<TypeIcon <TypeIcon

View File

@ -11,7 +11,7 @@ import UpdatedAt from './UpdatedAt';
import Touchable from './Touchable'; import Touchable from './Touchable';
import Tag from './Tag'; import Tag from './Tag';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { DISPLAY_MODE_EXPANDED } from '../../constants/constantDisplayMode'; import { DisplayMode } from '../../constants/constantDisplayMode';
interface IRoomItem { interface IRoomItem {
rid: string; rid: string;
@ -132,7 +132,7 @@ const RoomItem = ({
displayMode={displayMode} displayMode={displayMode}
showAvatar={showAvatar} showAvatar={showAvatar}
showLastMessage={showLastMessage}> showLastMessage={showLastMessage}>
{showLastMessage && displayMode === DISPLAY_MODE_EXPANDED ? ( {showLastMessage && displayMode === DisplayMode.Expanded ? (
<> <>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
{showAvatar ? ( {showAvatar ? (

View File

@ -2,7 +2,7 @@ import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { DISPLAY_MODE_CONDENSED } from '../../constants/constantDisplayMode'; import { DisplayMode } from '../../constants/constantDisplayMode';
import IconOrAvatar from './IconOrAvatar'; import IconOrAvatar from './IconOrAvatar';
import styles from './styles'; import styles from './styles';
@ -25,7 +25,7 @@ interface IWrapper {
const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }: IWrapper) => ( const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }: IWrapper) => (
<View <View
style={[styles.container, displayMode === DISPLAY_MODE_CONDENSED && styles.containerCondensed]} style={[styles.container, displayMode === DisplayMode.Condensed && styles.containerCondensed]}
accessibilityLabel={accessibilityLabel}> accessibilityLabel={accessibilityLabel}>
<IconOrAvatar theme={theme} displayMode={displayMode} {...props} /> <IconOrAvatar theme={theme} displayMode={displayMode} {...props} />
<View <View
@ -34,7 +34,7 @@ const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }:
{ {
borderColor: themes[theme].separatorColor borderColor: themes[theme].separatorColor
}, },
displayMode === DISPLAY_MODE_CONDENSED && styles.condensedPaddingVertical displayMode === DisplayMode.Condensed && styles.condensedPaddingVertical
]}> ]}>
{children} {children}
</View> </View>

View File

@ -18,9 +18,9 @@ interface IServerItem {
name: string; name: string;
}; };
onPress(): void; onPress(): void;
onLongPress(): void; onLongPress?(): void;
hasCheck: boolean; hasCheck?: boolean;
theme: string; theme?: string;
} }
const defaultLogo = require('../../static/images/logo.png'); const defaultLogo = require('../../static/images/logo.png');
@ -31,10 +31,10 @@ const ServerItem = React.memo(({ item, onPress, onLongPress, hasCheck, theme }:
onLongPress={() => onLongPress?.()} onLongPress={() => onLongPress?.()}
testID={`rooms-list-header-server-${item.id}`} testID={`rooms-list-header-server-${item.id}`}
android_ripple={{ android_ripple={{
color: themes[theme].bannerBackground color: themes[theme!].bannerBackground
}} }}
style={({ pressed }: any) => ({ style={({ pressed }: any) => ({
backgroundColor: isIOS && pressed ? themes[theme].bannerBackground : themes[theme].backgroundColor backgroundColor: isIOS && pressed ? themes[theme!].bannerBackground : themes[theme!].backgroundColor
})}> })}>
<View style={styles.serverItemContainer}> <View style={styles.serverItemContainer}>
{item.iconURL ? ( {item.iconURL ? (
@ -52,14 +52,14 @@ const ServerItem = React.memo(({ item, onPress, onLongPress, hasCheck, theme }:
<FastImage source={defaultLogo} style={styles.serverIcon} /> <FastImage source={defaultLogo} style={styles.serverIcon} />
)} )}
<View style={styles.serverTextContainer}> <View style={styles.serverTextContainer}>
<Text numberOfLines={1} style={[styles.serverName, { color: themes[theme].titleText }]}> <Text numberOfLines={1} style={[styles.serverName, { color: themes[theme!].titleText }]}>
{item.name || item.id} {item.name || item.id}
</Text> </Text>
<Text numberOfLines={1} style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}> <Text numberOfLines={1} style={[styles.serverUrl, { color: themes[theme!].auxiliaryText }]}>
{item.id} {item.id}
</Text> </Text>
</View> </View>
{hasCheck ? <Check theme={theme} /> : null} {hasCheck ? <Check theme={theme!} /> : null}
</View> </View>
</Pressable> </Pressable>
)); ));

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { I18nManager, StyleProp, StyleSheet, TextInput, TextInputProps, TextStyle } from 'react-native'; import { I18nManager, StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native';
import { IRCTextInputProps } from '../containers/TextInput';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -9,7 +10,7 @@ const styles = StyleSheet.create({
} }
}); });
interface IThemedTextInput extends TextInputProps { interface IThemedTextInput extends IRCTextInputProps {
style: StyleProp<TextStyle>; style: StyleProp<TextStyle>;
theme: string; theme: string;
} }

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View, ViewStyle } from 'react-native';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { getUnreadStyle } from './getUnreadStyle'; import { getUnreadStyle } from './getUnreadStyle';
@ -30,15 +30,15 @@ const styles = StyleSheet.create({
}); });
interface IUnreadBadge { interface IUnreadBadge {
theme: string; theme?: string;
unread: number; unread?: number;
userMentions: number; userMentions?: number;
groupMentions: number; groupMentions?: number;
style: object; style?: ViewStyle;
tunread: []; tunread?: [];
tunreadUser: []; tunreadUser?: [];
tunreadGroup: []; tunreadGroup?: [];
small: boolean; small?: boolean;
} }
const UnreadBadge = React.memo( const UnreadBadge = React.memo(

View File

@ -1,15 +0,0 @@
import { SET_ACTIVE_USERS } from '../actions/actionsTypes';
const initialState = {};
export default function activeUsers(state = initialState, action) {
switch (action.type) {
case SET_ACTIVE_USERS:
return {
...state,
...action.activeUsers
};
default:
return state;
}
}

Some files were not shown because too many files have changed in this diff Show More