Merge 4.24.0 into master (#3648)
This commit is contained in:
parent
857394aba8
commit
04db33a61e
|
@ -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': [
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
import { SET_ACTIVE_USERS } from './actionsTypes';
|
|
||||||
|
|
||||||
export function setActiveUsers(activeUsers) {
|
|
||||||
return {
|
|
||||||
type: SET_ACTIVE_USERS,
|
|
||||||
activeUsers
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
});
|
|
@ -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
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,2 +0,0 @@
|
||||||
export const DISPLAY_MODE_CONDENSED = 'condensed';
|
|
||||||
export const DISPLAY_MODE_EXPANDED = 'expanded';
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
export enum DisplayMode {
|
||||||
|
Condensed = 'condensed',
|
||||||
|
Expanded = 'expanded'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SortBy {
|
||||||
|
Alphabetical = 'alphabetical',
|
||||||
|
Activity = 'activity'
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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) => (
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -30,7 +30,7 @@ interface IMessageAudioProps {
|
||||||
};
|
};
|
||||||
theme: string;
|
theme: string;
|
||||||
getCustomEmoji: Function;
|
getCustomEmoji: Function;
|
||||||
scale: number;
|
scale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageAudioState {
|
interface IMessageAudioState {
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface IMessageReaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageReactions {
|
interface IMessageReactions {
|
||||||
reactions: object[];
|
reactions?: object[];
|
||||||
getCustomEmoji: Function;
|
getCustomEmoji: Function;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface ICommand {
|
||||||
|
event: {
|
||||||
|
input: string;
|
||||||
|
modifierFlags: number;
|
||||||
|
};
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IMention {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
type: string;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Model from '@nozbe/watermelondb/Model';
|
||||||
|
|
||||||
|
export interface IPermission {
|
||||||
|
id: string;
|
||||||
|
roles: string[];
|
||||||
|
_updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TPermissionModel = IPermission & Model;
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface IReaction {
|
||||||
|
_id: string;
|
||||||
|
emoji: string;
|
||||||
|
usernames: string[];
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
export interface IRocketChatRecord {
|
|
||||||
id: string;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Model from '@nozbe/watermelondb/Model';
|
||||||
|
|
||||||
|
export interface IRole {
|
||||||
|
id: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TRoleModel = IRole & Model;
|
|
@ -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;
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface IServedBy {
|
||||||
|
_id: string;
|
||||||
|
username: string;
|
||||||
|
ts: Date;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,8 @@
|
||||||
|
export type TThemeMode = 'automatic' | 'light' | 'dark';
|
||||||
|
|
||||||
|
export type TDarkLevel = 'black' | 'dark';
|
||||||
|
|
||||||
|
export interface IThemePreference {
|
||||||
|
currentTheme: TThemeMode;
|
||||||
|
darkLevel: TDarkLevel;
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IUrl {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
image: string;
|
||||||
|
url: string;
|
||||||
|
}
|
|
@ -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;
|
|
@ -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';
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 }) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -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();
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ? (
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
));
|
));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue