Chore: Migrate Utils Folder to Typescript (#3544)
Co-authored-by: AlexAlexandre <alexalexandrejr@gmail.com>
This commit is contained in:
parent
f7418791a2
commit
a2b92f5e70
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -84,7 +84,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface ICommand {
|
||||||
|
event: {
|
||||||
|
input: string;
|
||||||
|
modifierFlags: number;
|
||||||
|
};
|
||||||
|
}
|
|
@ -10,8 +10,8 @@ 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;
|
||||||
|
|
|
@ -79,6 +79,8 @@ export interface ISubscription {
|
||||||
avatarETag?: string;
|
avatarETag?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
teamMain?: boolean;
|
teamMain?: boolean;
|
||||||
|
search?: boolean;
|
||||||
|
username?: string;
|
||||||
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
|
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
|
||||||
messages: Relation<TMessageModel>;
|
messages: Relation<TMessageModel>;
|
||||||
threads: Relation<TThreadModel>;
|
threads: Relation<TThreadModel>;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export type TThemeMode = 'automatic' | 'light' | 'dark';
|
||||||
|
|
||||||
|
export type TDarkLevel = 'black' | 'dark';
|
||||||
|
|
||||||
|
export interface IThemePreference {
|
||||||
|
currentTheme: TThemeMode;
|
||||||
|
darkLevel: TDarkLevel;
|
||||||
|
}
|
|
@ -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';
|
||||||
|
|
|
@ -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 });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { defaultHeader, getActiveRouteName, navigationTheme, themedHeader } from
|
||||||
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
|
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
|
||||||
import { ThemeContext } from './theme';
|
import { ThemeContext } from './theme';
|
||||||
import { localAuthenticate } from './utils/localAuthentication';
|
import { localAuthenticate } from './utils/localAuthentication';
|
||||||
|
import { IThemePreference } from './definitions/ITheme';
|
||||||
import ScreenLockedView from './views/ScreenLockedView';
|
import ScreenLockedView from './views/ScreenLockedView';
|
||||||
// Outside Stack
|
// Outside Stack
|
||||||
import WithoutServersView from './views/WithoutServersView';
|
import WithoutServersView from './views/WithoutServersView';
|
||||||
|
@ -36,10 +37,7 @@ interface IDimensions {
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
theme: string;
|
theme: string;
|
||||||
themePreferences: {
|
themePreferences: IThemePreference;
|
||||||
currentTheme: 'automatic' | 'light';
|
|
||||||
darkLevel: string;
|
|
||||||
};
|
|
||||||
root: any;
|
root: any;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
@ -135,7 +133,7 @@ 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
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import hoistNonReactStatics from 'hoist-non-react-statics';
|
import hoistNonReactStatics from 'hoist-non-react-statics';
|
||||||
|
|
||||||
|
import { IThemePreference } from './definitions/ITheme';
|
||||||
|
|
||||||
interface IThemeContextProps {
|
interface IThemeContextProps {
|
||||||
theme: string;
|
theme: string;
|
||||||
themePreferences?: {
|
themePreferences?: IThemePreference;
|
||||||
currentTheme: 'automatic' | 'light';
|
|
||||||
darkLevel: string;
|
|
||||||
};
|
|
||||||
setTheme?: (newTheme?: {}) => void;
|
setTheme?: (newTheme?: {}) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { isIOS } from './deviceInfo';
|
||||||
|
|
||||||
const { AppGroup } = NativeModules;
|
const { AppGroup } = NativeModules;
|
||||||
|
|
||||||
const appGroup = {
|
const appGroup: { path: string } = {
|
||||||
path: isIOS ? AppGroup.path : ''
|
path: isIOS ? AppGroup.path : ''
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { compareServerVersion, methods } from '../lib/utils';
|
import { compareServerVersion, methods } from '../lib/utils';
|
||||||
|
import { SubscriptionType } from '../definitions/ISubscription';
|
||||||
|
import { IAvatar } from '../containers/Avatar/interfaces';
|
||||||
|
|
||||||
const formatUrl = (url, size, query) => `${url}?format=png&size=${size}${query}`;
|
const formatUrl = (url: string, size: number, query: string) => `${url}?format=png&size=${size}${query}`;
|
||||||
|
|
||||||
export const avatarURL = ({
|
export const avatarURL = ({
|
||||||
type,
|
type,
|
||||||
|
@ -13,9 +15,9 @@ export const avatarURL = ({
|
||||||
rid,
|
rid,
|
||||||
blockUnauthenticatedAccess,
|
blockUnauthenticatedAccess,
|
||||||
serverVersion
|
serverVersion
|
||||||
}) => {
|
}: IAvatar): string => {
|
||||||
let room;
|
let room;
|
||||||
if (type === 'd') {
|
if (type === SubscriptionType.DIRECT) {
|
||||||
room = text;
|
room = text;
|
||||||
} else if (rid && !compareServerVersion(serverVersion, '3.6.0', methods.lowerThan)) {
|
} else if (rid && !compareServerVersion(serverVersion, '3.6.0', methods.lowerThan)) {
|
||||||
room = `room/${rid}`;
|
room = `room/${rid}`;
|
|
@ -1,8 +1,8 @@
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
// https://github.com/beatgammit/base64-js/blob/master/index.js
|
// https://github.com/beatgammit/base64-js/blob/master/index.js
|
||||||
|
|
||||||
const lookup = [];
|
const lookup: string[] = [];
|
||||||
const revLookup = [];
|
const revLookup: number[] = [];
|
||||||
const Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;
|
const Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;
|
||||||
|
|
||||||
const code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
const code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||||
|
@ -16,7 +16,7 @@ for (let i = 0, len = code.length; i < len; i += 1) {
|
||||||
revLookup['-'.charCodeAt(0)] = 62;
|
revLookup['-'.charCodeAt(0)] = 62;
|
||||||
revLookup['_'.charCodeAt(0)] = 63;
|
revLookup['_'.charCodeAt(0)] = 63;
|
||||||
|
|
||||||
const getLens = b64 => {
|
const getLens = (b64: string) => {
|
||||||
const len = b64.length;
|
const len = b64.length;
|
||||||
|
|
||||||
// We're encoding some strings not multiple of 4, so, disable this check
|
// We're encoding some strings not multiple of 4, so, disable this check
|
||||||
|
@ -37,16 +37,17 @@ const getLens = b64 => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// base64 is 4/3 + up to two characters of the original data
|
// base64 is 4/3 + up to two characters of the original data
|
||||||
export const byteLength = b64 => {
|
export const byteLength = (b64: string) => {
|
||||||
const lens = getLens(b64);
|
const lens = getLens(b64);
|
||||||
const validLen = lens[0];
|
const validLen = lens[0];
|
||||||
const placeHoldersLen = lens[1];
|
const placeHoldersLen = lens[1];
|
||||||
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
|
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _byteLength = (b64, validLen, placeHoldersLen) => ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
|
const _byteLength = (b64: string, validLen: number, placeHoldersLen: number) =>
|
||||||
|
((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
|
||||||
|
|
||||||
export const toByteArray = b64 => {
|
export const toByteArray = (b64: string) => {
|
||||||
let tmp;
|
let tmp;
|
||||||
const lens = getLens(b64);
|
const lens = getLens(b64);
|
||||||
const validLen = lens[0];
|
const validLen = lens[0];
|
||||||
|
@ -92,10 +93,10 @@ export const toByteArray = b64 => {
|
||||||
return arr;
|
return arr;
|
||||||
};
|
};
|
||||||
|
|
||||||
const tripletToBase64 = num =>
|
const tripletToBase64 = (num: number) =>
|
||||||
lookup[(num >> 18) & 0x3f] + lookup[(num >> 12) & 0x3f] + lookup[(num >> 6) & 0x3f] + lookup[num & 0x3f];
|
lookup[(num >> 18) & 0x3f] + lookup[(num >> 12) & 0x3f] + lookup[(num >> 6) & 0x3f] + lookup[num & 0x3f];
|
||||||
|
|
||||||
const encodeChunk = (uint8, start, end) => {
|
const encodeChunk = (uint8: number[] | Uint8Array, start: number, end: number) => {
|
||||||
let tmp;
|
let tmp;
|
||||||
const output = [];
|
const output = [];
|
||||||
for (let i = start; i < end; i += 3) {
|
for (let i = start; i < end; i += 3) {
|
||||||
|
@ -105,7 +106,7 @@ const encodeChunk = (uint8, start, end) => {
|
||||||
return output.join('');
|
return output.join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fromByteArray = uint8 => {
|
export const fromByteArray = (uint8: number[] | Uint8Array) => {
|
||||||
let tmp;
|
let tmp;
|
||||||
const len = uint8.length;
|
const len = uint8.length;
|
||||||
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
|
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
|
|
@ -1,20 +0,0 @@
|
||||||
export default function debounce(func, wait, immediate) {
|
|
||||||
let timeout;
|
|
||||||
function _debounce(...args) {
|
|
||||||
const context = this;
|
|
||||||
const later = function __debounce() {
|
|
||||||
timeout = null;
|
|
||||||
if (!immediate) {
|
|
||||||
func.apply(context, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const callNow = immediate && !timeout;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(later, wait);
|
|
||||||
if (callNow) {
|
|
||||||
func.apply(context, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_debounce.stop = () => clearTimeout(timeout);
|
|
||||||
return _debounce;
|
|
||||||
}
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
export default function debounce(func: Function, wait?: number, immediate?: boolean) {
|
||||||
|
let timeout: number | null;
|
||||||
|
function _debounce(...args: any[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
|
const context = this;
|
||||||
|
const later = function __debounce() {
|
||||||
|
timeout = null;
|
||||||
|
if (!immediate) {
|
||||||
|
func.apply(context, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const callNow = immediate && !timeout;
|
||||||
|
clearTimeout(timeout!);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
if (callNow) {
|
||||||
|
func.apply(context, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_debounce.stop = () => clearTimeout(timeout!);
|
||||||
|
return _debounce;
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ export const getBundleId = DeviceInfo.getBundleId();
|
||||||
export const getDeviceModel = DeviceInfo.getModel();
|
export const getDeviceModel = DeviceInfo.getModel();
|
||||||
|
|
||||||
// Theme is supported by system on iOS 13+ or Android 10+
|
// Theme is supported by system on iOS 13+ or Android 10+
|
||||||
export const supportSystemTheme = () => {
|
export const supportSystemTheme = (): boolean => {
|
||||||
const systemVersion = parseInt(DeviceInfo.getSystemVersion(), 10);
|
const systemVersion = parseInt(DeviceInfo.getSystemVersion(), 10);
|
||||||
return systemVersion >= (isIOS ? 13 : 10);
|
return systemVersion >= (isIOS ? 13 : 10);
|
||||||
};
|
};
|
|
@ -1,11 +1,25 @@
|
||||||
|
import { ICommand } from '../definitions/ICommand';
|
||||||
import log from './log';
|
import log from './log';
|
||||||
|
|
||||||
|
type TEventEmitterEmmitArgs =
|
||||||
|
| { rid: string }
|
||||||
|
| { message: string }
|
||||||
|
| { method: string }
|
||||||
|
| { invalid: boolean }
|
||||||
|
| { force: boolean }
|
||||||
|
| { hasBiometry: boolean }
|
||||||
|
| { event: string | ICommand }
|
||||||
|
| { cancel: () => void }
|
||||||
|
| { submit: (param: string) => void };
|
||||||
|
|
||||||
class EventEmitter {
|
class EventEmitter {
|
||||||
|
private events: { [key: string]: any };
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.events = {};
|
this.events = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener(event, listener) {
|
addEventListener(event: string, listener: Function) {
|
||||||
if (typeof this.events[event] !== 'object') {
|
if (typeof this.events[event] !== 'object') {
|
||||||
this.events[event] = [];
|
this.events[event] = [];
|
||||||
}
|
}
|
||||||
|
@ -13,7 +27,7 @@ class EventEmitter {
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeListener(event, listener) {
|
removeListener(event: string, listener: Function) {
|
||||||
if (typeof this.events[event] === 'object') {
|
if (typeof this.events[event] === 'object') {
|
||||||
const idx = this.events[event].indexOf(listener);
|
const idx = this.events[event].indexOf(listener);
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
|
@ -25,9 +39,9 @@ class EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(event, ...args) {
|
emit(event: string, ...args: TEventEmitterEmmitArgs[]) {
|
||||||
if (typeof this.events[event] === 'object') {
|
if (typeof this.events[event] === 'object') {
|
||||||
this.events[event].forEach(listener => {
|
this.events[event].forEach((listener: Function) => {
|
||||||
try {
|
try {
|
||||||
listener.apply(this, args);
|
listener.apply(this, args);
|
||||||
} catch (e) {
|
} catch (e) {
|
|
@ -4,15 +4,20 @@ import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||||
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
|
||||||
|
interface CustomHeaders {
|
||||||
|
'User-Agent': string;
|
||||||
|
Authorization?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// this form is required by Rocket.Chat's parser in "app/statistics/server/lib/UAParserCustom.js"
|
// this form is required by Rocket.Chat's parser in "app/statistics/server/lib/UAParserCustom.js"
|
||||||
export const headers = {
|
export const headers: CustomHeaders = {
|
||||||
'User-Agent': `RC Mobile; ${
|
'User-Agent': `RC Mobile; ${
|
||||||
Platform.OS
|
Platform.OS
|
||||||
} ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})`
|
} ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})`
|
||||||
};
|
};
|
||||||
|
|
||||||
let _basicAuth;
|
let _basicAuth;
|
||||||
export const setBasicAuth = basicAuth => {
|
export const setBasicAuth = (basicAuth: string): void => {
|
||||||
_basicAuth = basicAuth;
|
_basicAuth = basicAuth;
|
||||||
if (basicAuth) {
|
if (basicAuth) {
|
||||||
RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` };
|
RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` };
|
||||||
|
@ -24,12 +29,15 @@ export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY';
|
||||||
|
|
||||||
RocketChatSettings.customHeaders = headers;
|
RocketChatSettings.customHeaders = headers;
|
||||||
|
|
||||||
export default (url, options = {}) => {
|
export default (url: string, options: { headers?: Headers; signal?: AbortSignal } = {}): Promise<Response> => {
|
||||||
let customOptions = { ...options, headers: RocketChatSettings.customHeaders };
|
let customOptions = { ...options, headers: RocketChatSettings.customHeaders };
|
||||||
if (options && options.headers) {
|
if (options && options.headers) {
|
||||||
customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } };
|
customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } };
|
||||||
}
|
}
|
||||||
|
// TODO: Refactor when migrate rocketchat.js
|
||||||
|
// @ts-ignore
|
||||||
if (RocketChat.controller) {
|
if (RocketChat.controller) {
|
||||||
|
// @ts-ignore
|
||||||
const { signal } = RocketChat.controller;
|
const { signal } = RocketChat.controller;
|
||||||
customOptions = { ...customOptions, signal };
|
customOptions = { ...customOptions, signal };
|
||||||
}
|
}
|
|
@ -1,19 +1,25 @@
|
||||||
|
import { IFileUpload } from './interfaces';
|
||||||
|
|
||||||
class Upload {
|
class Upload {
|
||||||
|
public xhr: XMLHttpRequest;
|
||||||
|
|
||||||
|
public formData: FormData;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.xhr = new XMLHttpRequest();
|
this.xhr = new XMLHttpRequest();
|
||||||
this.formData = new FormData();
|
this.formData = new FormData();
|
||||||
}
|
}
|
||||||
|
|
||||||
then = callback => {
|
then = (callback: (param: { respInfo: XMLHttpRequest }) => XMLHttpRequest) => {
|
||||||
this.xhr.onload = () => callback({ respInfo: this.xhr });
|
this.xhr.onload = () => callback({ respInfo: this.xhr });
|
||||||
this.xhr.send(this.formData);
|
this.xhr.send(this.formData);
|
||||||
};
|
};
|
||||||
|
|
||||||
catch = callback => {
|
catch = (callback: ((this: XMLHttpRequest, ev: ProgressEvent<EventTarget>) => any) | null) => {
|
||||||
this.xhr.onerror = callback;
|
this.xhr.onerror = callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
uploadProgress = callback => {
|
uploadProgress = (callback: (param: number, arg1: number) => any) => {
|
||||||
this.xhr.upload.onprogress = ({ total, loaded }) => callback(loaded, total);
|
this.xhr.upload.onprogress = ({ total, loaded }) => callback(loaded, total);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,7 +30,7 @@ class Upload {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileUpload {
|
class FileUpload {
|
||||||
fetch = (method, url, headers, data) => {
|
fetch = (method: string, url: string, headers: { [x: string]: string }, data: IFileUpload[]) => {
|
||||||
const upload = new Upload();
|
const upload = new Upload();
|
||||||
upload.xhr.open(method, url);
|
upload.xhr.open(method, url);
|
||||||
|
|
||||||
|
@ -35,6 +41,7 @@ class FileUpload {
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
if (item.uri) {
|
if (item.uri) {
|
||||||
upload.formData.append(item.name, {
|
upload.formData.append(item.name, {
|
||||||
|
// @ts-ignore
|
||||||
uri: item.uri,
|
uri: item.uri,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
name: item.filename
|
name: item.filename
|
|
@ -1,7 +1,11 @@
|
||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import RNFetchBlob from 'rn-fetch-blob';
|
||||||
|
|
||||||
|
import { IFileUpload } from './interfaces';
|
||||||
|
|
||||||
|
type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put';
|
||||||
|
|
||||||
class FileUpload {
|
class FileUpload {
|
||||||
fetch = (method, url, headers, data) => {
|
fetch = (method: TMethods, url: string, headers: { [key: string]: string }, data: IFileUpload[]) => {
|
||||||
const formData = data.map(item => {
|
const formData = data.map(item => {
|
||||||
if (item.uri) {
|
if (item.uri) {
|
||||||
return {
|
return {
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface IFileUpload {
|
||||||
|
name: string;
|
||||||
|
uri?: string;
|
||||||
|
type: string;
|
||||||
|
filename: string;
|
||||||
|
data: any;
|
||||||
|
}
|
|
@ -1,7 +1,17 @@
|
||||||
|
import { ChatsStackParamList } from '../stacks/types';
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import { ISubscription, SubscriptionType } from '../definitions/ISubscription';
|
||||||
|
|
||||||
const navigate = ({ item, isMasterDetail, ...props }) => {
|
const navigate = ({
|
||||||
|
item,
|
||||||
|
isMasterDetail,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
item: IItem;
|
||||||
|
isMasterDetail: boolean;
|
||||||
|
navigationMethod?: () => ChatsStackParamList;
|
||||||
|
}) => {
|
||||||
let navigationMethod = props.navigationMethod ?? Navigation.navigate;
|
let navigationMethod = props.navigationMethod ?? Navigation.navigate;
|
||||||
|
|
||||||
if (isMasterDetail) {
|
if (isMasterDetail) {
|
||||||
|
@ -20,7 +30,22 @@ const navigate = ({ item, isMasterDetail, ...props }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const goRoom = async ({ item = {}, isMasterDetail = false, ...props }) => {
|
interface IItem extends Partial<ISubscription> {
|
||||||
|
rid: string;
|
||||||
|
name: string;
|
||||||
|
t: SubscriptionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const goRoom = async ({
|
||||||
|
item,
|
||||||
|
isMasterDetail = false,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
item: IItem;
|
||||||
|
isMasterDetail: boolean;
|
||||||
|
navigationMethod?: any;
|
||||||
|
jumpToMessageId?: string;
|
||||||
|
}): Promise<void> => {
|
||||||
if (item.t === 'd' && item.search) {
|
if (item.t === 'd' && item.search) {
|
||||||
// if user is using the search we need first to join/create room
|
// if user is using the search we need first to join/create room
|
||||||
try {
|
try {
|
||||||
|
@ -30,8 +55,8 @@ export const goRoom = async ({ item = {}, isMasterDetail = false, ...props }) =>
|
||||||
return navigate({
|
return navigate({
|
||||||
item: {
|
item: {
|
||||||
rid: result.room._id,
|
rid: result.room._id,
|
||||||
name: username,
|
name: username!,
|
||||||
t: 'd'
|
t: SubscriptionType.DIRECT
|
||||||
},
|
},
|
||||||
isMasterDetail,
|
isMasterDetail,
|
||||||
...props
|
...props
|
|
@ -1,25 +0,0 @@
|
||||||
import { Alert } from 'react-native';
|
|
||||||
|
|
||||||
import I18n from '../i18n';
|
|
||||||
|
|
||||||
export const showErrorAlert = (message, title, onPress = () => {}) =>
|
|
||||||
Alert.alert(title, message, [{ text: 'OK', onPress }], { cancelable: true });
|
|
||||||
|
|
||||||
export const showConfirmationAlert = ({ title, message, confirmationText, dismissText = I18n.t('Cancel'), onPress, onCancel }) =>
|
|
||||||
Alert.alert(
|
|
||||||
title || I18n.t('Are_you_sure_question_mark'),
|
|
||||||
message,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: dismissText,
|
|
||||||
onPress: onCancel,
|
|
||||||
style: 'cancel'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: confirmationText,
|
|
||||||
style: 'destructive',
|
|
||||||
onPress
|
|
||||||
}
|
|
||||||
],
|
|
||||||
{ cancelable: false }
|
|
||||||
);
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { Alert } from 'react-native';
|
||||||
|
|
||||||
|
import I18n from '../i18n';
|
||||||
|
|
||||||
|
export const showErrorAlert = (message: string, title?: string, onPress = () => {}): void =>
|
||||||
|
Alert.alert(title!, message, [{ text: 'OK', onPress }], { cancelable: true });
|
||||||
|
|
||||||
|
interface IShowConfirmationAlert {
|
||||||
|
title?: string;
|
||||||
|
message: string;
|
||||||
|
confirmationText: string;
|
||||||
|
dismissText?: string;
|
||||||
|
onPress: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showConfirmationAlert = ({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
confirmationText,
|
||||||
|
dismissText = I18n.t('Cancel'),
|
||||||
|
onPress,
|
||||||
|
onCancel
|
||||||
|
}: IShowConfirmationAlert): void =>
|
||||||
|
Alert.alert(
|
||||||
|
title || I18n.t('Are_you_sure_question_mark'),
|
||||||
|
message,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: dismissText,
|
||||||
|
onPress: onCancel,
|
||||||
|
style: 'cancel'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: confirmationText,
|
||||||
|
style: 'destructive',
|
||||||
|
onPress
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{ cancelable: false }
|
||||||
|
);
|
|
@ -1,16 +1,21 @@
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import reduxStore from '../lib/createStore';
|
import reduxStore from '../lib/createStore';
|
||||||
|
import { ISubscription } from '../definitions/ISubscription';
|
||||||
|
|
||||||
const canPostReadOnly = async ({ rid }) => {
|
const canPostReadOnly = async ({ rid }: { rid: string }) => {
|
||||||
// TODO: this is not reactive. If this permission changes, the component won't be updated
|
// TODO: this is not reactive. If this permission changes, the component won't be updated
|
||||||
const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly'];
|
const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly'];
|
||||||
const permission = await RocketChat.hasPermission([postReadOnlyPermission], rid);
|
const permission = await RocketChat.hasPermission([postReadOnlyPermission], rid);
|
||||||
return permission[0];
|
return permission[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMuted = (room, user) => room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username);
|
const isMuted = (room: ISubscription, user: { username: string }) =>
|
||||||
|
room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username);
|
||||||
|
|
||||||
export const isReadOnly = async (room, user) => {
|
export const isReadOnly = async (
|
||||||
|
room: ISubscription,
|
||||||
|
user: { id?: string; username: string; token?: string }
|
||||||
|
): Promise<boolean> => {
|
||||||
if (room.archived) {
|
if (room.archived) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
export default function isValidEmail(email) {
|
export default function isValidEmail(email: string): boolean {
|
||||||
/* eslint-disable no-useless-escape */
|
/* eslint-disable no-useless-escape */
|
||||||
const reg =
|
const reg =
|
||||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
@ -16,16 +16,17 @@ import {
|
||||||
} from '../constants/localAuthentication';
|
} from '../constants/localAuthentication';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { setLocalAuthenticated } from '../actions/login';
|
import { setLocalAuthenticated } from '../actions/login';
|
||||||
|
import { TServerModel } from '../definitions/IServer';
|
||||||
import EventEmitter from './events';
|
import EventEmitter from './events';
|
||||||
import { isIOS } from './deviceInfo';
|
import { isIOS } from './deviceInfo';
|
||||||
|
|
||||||
export const saveLastLocalAuthenticationSession = async (server, serverRecord) => {
|
export const saveLastLocalAuthenticationSession = async (server: string, serverRecord?: TServerModel): Promise<void> => {
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
const serversCollection = serversDB.get('servers');
|
const serversCollection = serversDB.get('servers');
|
||||||
await serversDB.action(async () => {
|
await serversDB.write(async () => {
|
||||||
try {
|
try {
|
||||||
if (!serverRecord) {
|
if (!serverRecord) {
|
||||||
serverRecord = await serversCollection.find(server);
|
serverRecord = (await serversCollection.find(server)) as TServerModel;
|
||||||
}
|
}
|
||||||
await serverRecord.update(record => {
|
await serverRecord.update(record => {
|
||||||
record.lastLocalAuthenticatedSession = new Date();
|
record.lastLocalAuthenticatedSession = new Date();
|
||||||
|
@ -36,31 +37,31 @@ export const saveLastLocalAuthenticationSession = async (server, serverRecord) =
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resetAttempts = () => AsyncStorage.multiRemove([LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY]);
|
export const resetAttempts = (): Promise<void> => AsyncStorage.multiRemove([LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY]);
|
||||||
|
|
||||||
const openModal = hasBiometry =>
|
const openModal = (hasBiometry: boolean) =>
|
||||||
new Promise(resolve => {
|
new Promise<void>(resolve => {
|
||||||
EventEmitter.emit(LOCAL_AUTHENTICATE_EMITTER, {
|
EventEmitter.emit(LOCAL_AUTHENTICATE_EMITTER, {
|
||||||
submit: () => resolve(),
|
submit: () => resolve(),
|
||||||
hasBiometry
|
hasBiometry
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const openChangePasscodeModal = ({ force }) =>
|
const openChangePasscodeModal = ({ force }: { force: boolean }) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise<string>((resolve, reject) => {
|
||||||
EventEmitter.emit(CHANGE_PASSCODE_EMITTER, {
|
EventEmitter.emit(CHANGE_PASSCODE_EMITTER, {
|
||||||
submit: passcode => resolve(passcode),
|
submit: (passcode: string) => resolve(passcode),
|
||||||
cancel: () => reject(),
|
cancel: () => reject(),
|
||||||
force
|
force
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export const changePasscode = async ({ force = false }) => {
|
export const changePasscode = async ({ force = false }: { force: boolean }): Promise<void> => {
|
||||||
const passcode = await openChangePasscodeModal({ force });
|
const passcode = await openChangePasscodeModal({ force });
|
||||||
await UserPreferences.setStringAsync(PASSCODE_KEY, sha256(passcode));
|
await UserPreferences.setStringAsync(PASSCODE_KEY, sha256(passcode));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const biometryAuth = force =>
|
export const biometryAuth = (force?: boolean): Promise<LocalAuthentication.LocalAuthenticationResult> =>
|
||||||
LocalAuthentication.authenticateAsync({
|
LocalAuthentication.authenticateAsync({
|
||||||
disableDeviceFallback: true,
|
disableDeviceFallback: true,
|
||||||
cancelLabel: force ? I18n.t('Dont_activate') : I18n.t('Local_authentication_biometry_fallback'),
|
cancelLabel: force ? I18n.t('Dont_activate') : I18n.t('Local_authentication_biometry_fallback'),
|
||||||
|
@ -71,11 +72,11 @@ export const biometryAuth = force =>
|
||||||
* It'll help us to get the permission to use FaceID
|
* It'll help us to get the permission to use FaceID
|
||||||
* and enable/disable the biometry when user put their first passcode
|
* and enable/disable the biometry when user put their first passcode
|
||||||
*/
|
*/
|
||||||
const checkBiometry = async serverRecord => {
|
const checkBiometry = async (serverRecord: TServerModel) => {
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
|
|
||||||
const result = await biometryAuth(true);
|
const result = await biometryAuth(true);
|
||||||
await serversDB.action(async () => {
|
await serversDB.write(async () => {
|
||||||
try {
|
try {
|
||||||
await serverRecord.update(record => {
|
await serverRecord.update(record => {
|
||||||
record.biometry = !!result?.success;
|
record.biometry = !!result?.success;
|
||||||
|
@ -86,7 +87,13 @@ const checkBiometry = async serverRecord => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkHasPasscode = async ({ force = true, serverRecord }) => {
|
export const checkHasPasscode = async ({
|
||||||
|
force = true,
|
||||||
|
serverRecord
|
||||||
|
}: {
|
||||||
|
force?: boolean;
|
||||||
|
serverRecord: TServerModel;
|
||||||
|
}): Promise<{ newPasscode?: boolean } | void> => {
|
||||||
const storedPasscode = await UserPreferences.getStringAsync(PASSCODE_KEY);
|
const storedPasscode = await UserPreferences.getStringAsync(PASSCODE_KEY);
|
||||||
if (!storedPasscode) {
|
if (!storedPasscode) {
|
||||||
await changePasscode({ force });
|
await changePasscode({ force });
|
||||||
|
@ -96,13 +103,13 @@ export const checkHasPasscode = async ({ force = true, serverRecord }) => {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const localAuthenticate = async server => {
|
export const localAuthenticate = async (server: string): Promise<void> => {
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
const serversCollection = serversDB.get('servers');
|
const serversCollection = serversDB.get('servers');
|
||||||
|
|
||||||
let serverRecord;
|
let serverRecord: TServerModel;
|
||||||
try {
|
try {
|
||||||
serverRecord = await serversCollection.find(server);
|
serverRecord = (await serversCollection.find(server)) as TServerModel;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
|
@ -125,7 +132,7 @@ export const localAuthenticate = async server => {
|
||||||
const diffToLastSession = moment().diff(serverRecord?.lastLocalAuthenticatedSession, 'seconds');
|
const diffToLastSession = moment().diff(serverRecord?.lastLocalAuthenticatedSession, 'seconds');
|
||||||
|
|
||||||
// if last authenticated session is older than configured auto lock time, authentication is required
|
// if last authenticated session is older than configured auto lock time, authentication is required
|
||||||
if (diffToLastSession >= serverRecord?.autoLockTime) {
|
if (diffToLastSession >= serverRecord.autoLockTime!) {
|
||||||
// set isLocalAuthenticated to false
|
// set isLocalAuthenticated to false
|
||||||
store.dispatch(setLocalAuthenticated(false));
|
store.dispatch(setLocalAuthenticated(false));
|
||||||
|
|
||||||
|
@ -150,7 +157,7 @@ export const localAuthenticate = async server => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const supportedBiometryLabel = async () => {
|
export const supportedBiometryLabel = async (): Promise<string | null> => {
|
||||||
try {
|
try {
|
||||||
const enrolled = await LocalAuthentication.isEnrolledAsync();
|
const enrolled = await LocalAuthentication.isEnrolledAsync();
|
||||||
|
|
|
@ -4,13 +4,13 @@ import { isFDroidBuild } from '../../constants/environment';
|
||||||
import events from './events';
|
import events from './events';
|
||||||
|
|
||||||
const analytics = firebaseAnalytics || '';
|
const analytics = firebaseAnalytics || '';
|
||||||
let bugsnag = '';
|
let bugsnag: any = '';
|
||||||
let crashlytics;
|
let crashlytics: any;
|
||||||
let reportCrashErrors = true;
|
let reportCrashErrors = true;
|
||||||
let reportAnalyticsEvents = true;
|
let reportAnalyticsEvents = true;
|
||||||
|
|
||||||
export const getReportCrashErrorsValue = () => reportCrashErrors;
|
export const getReportCrashErrorsValue = (): boolean => reportCrashErrors;
|
||||||
export const getReportAnalyticsEventsValue = () => reportAnalyticsEvents;
|
export const getReportAnalyticsEventsValue = (): boolean => reportAnalyticsEvents;
|
||||||
|
|
||||||
if (!isFDroidBuild) {
|
if (!isFDroidBuild) {
|
||||||
bugsnag = require('@bugsnag/react-native').default;
|
bugsnag = require('@bugsnag/react-native').default;
|
||||||
|
@ -18,7 +18,7 @@ if (!isFDroidBuild) {
|
||||||
onBreadcrumb() {
|
onBreadcrumb() {
|
||||||
return reportAnalyticsEvents;
|
return reportAnalyticsEvents;
|
||||||
},
|
},
|
||||||
onError(error) {
|
onError(error: { breadcrumbs: string[] }) {
|
||||||
if (!reportAnalyticsEvents) {
|
if (!reportAnalyticsEvents) {
|
||||||
error.breadcrumbs = [];
|
error.breadcrumbs = [];
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,13 @@ export { events };
|
||||||
|
|
||||||
let metadata = {};
|
let metadata = {};
|
||||||
|
|
||||||
export const logServerVersion = serverVersion => {
|
export const logServerVersion = (serverVersion: string): void => {
|
||||||
metadata = {
|
metadata = {
|
||||||
serverVersion
|
serverVersion
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logEvent = (eventName, payload) => {
|
export const logEvent = (eventName: string, payload?: { [key: string]: any }): void => {
|
||||||
try {
|
try {
|
||||||
if (!isFDroidBuild) {
|
if (!isFDroidBuild) {
|
||||||
analytics().logEvent(eventName, payload);
|
analytics().logEvent(eventName, payload);
|
||||||
|
@ -51,26 +51,26 @@ export const logEvent = (eventName, payload) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setCurrentScreen = currentScreen => {
|
export const setCurrentScreen = (currentScreen: string): void => {
|
||||||
if (!isFDroidBuild) {
|
if (!isFDroidBuild) {
|
||||||
analytics().setCurrentScreen(currentScreen);
|
analytics().setCurrentScreen(currentScreen);
|
||||||
bugsnag.leaveBreadcrumb(currentScreen, { type: 'navigation' });
|
bugsnag.leaveBreadcrumb(currentScreen, { type: 'navigation' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toggleCrashErrorsReport = value => {
|
export const toggleCrashErrorsReport = (value: boolean): boolean => {
|
||||||
crashlytics().setCrashlyticsCollectionEnabled(value);
|
crashlytics().setCrashlyticsCollectionEnabled(value);
|
||||||
return (reportCrashErrors = value);
|
return (reportCrashErrors = value);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toggleAnalyticsEventsReport = value => {
|
export const toggleAnalyticsEventsReport = (value: boolean): boolean => {
|
||||||
analytics().setAnalyticsCollectionEnabled(value);
|
analytics().setAnalyticsCollectionEnabled(value);
|
||||||
return (reportAnalyticsEvents = value);
|
return (reportAnalyticsEvents = value);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default e => {
|
export default (e: any): void => {
|
||||||
if (e instanceof Error && bugsnag && e.message !== 'Aborted' && !__DEV__) {
|
if (e instanceof Error && bugsnag && e.message !== 'Aborted' && !__DEV__) {
|
||||||
bugsnag.notify(e, event => {
|
bugsnag.notify(e, (event: { addMetadata: (arg0: string, arg1: {}) => void }) => {
|
||||||
event.addMetadata('details', { ...metadata });
|
event.addMetadata('details', { ...metadata });
|
||||||
});
|
});
|
||||||
if (!isFDroidBuild) {
|
if (!isFDroidBuild) {
|
|
@ -1,4 +1,11 @@
|
||||||
export const canUploadFile = (file, allowList, maxFileSize, permissionToUploadFile) => {
|
import { IAttachment } from '../views/ShareView/interfaces';
|
||||||
|
|
||||||
|
export const canUploadFile = (
|
||||||
|
file: IAttachment,
|
||||||
|
allowList: string,
|
||||||
|
maxFileSize: number,
|
||||||
|
permissionToUploadFile: boolean
|
||||||
|
): { success: boolean; error?: string } => {
|
||||||
if (!(file && file.path)) {
|
if (!(file && file.path)) {
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
@ -13,11 +20,11 @@ export const canUploadFile = (file, allowList, maxFileSize, permissionToUploadFi
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
const allowedMime = allowList.split(',');
|
const allowedMime = allowList.split(',');
|
||||||
if (allowedMime.includes(file.mime)) {
|
if (allowedMime.includes(file.mime!)) {
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
const wildCardGlob = '/*';
|
const wildCardGlob = '/*';
|
||||||
const wildCards = allowedMime.filter(item => item.indexOf(wildCardGlob) > 0);
|
const wildCards = allowedMime.filter((item: string) => item.indexOf(wildCardGlob) > 0);
|
||||||
if (file.mime && wildCards.includes(file.mime.replace(/(\/.*)$/, wildCardGlob))) {
|
if (file.mime && wildCards.includes(file.mime.replace(/(\/.*)$/, wildCardGlob))) {
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
const localeKeys = {
|
const localeKeys: { [key: string]: string } = {
|
||||||
en: 'en',
|
en: 'en',
|
||||||
ru: 'ru',
|
ru: 'ru',
|
||||||
'pt-BR': 'pt-br',
|
'pt-BR': 'pt-br',
|
||||||
|
@ -13,4 +13,4 @@ const localeKeys = {
|
||||||
'zh-TW': 'zh-tw'
|
'zh-TW': 'zh-tw'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toMomentLocale = locale => localeKeys[locale];
|
export const toMomentLocale = (locale: string): string => localeKeys[locale];
|
|
@ -1,12 +1,14 @@
|
||||||
import { Animated, Easing } from 'react-native';
|
import { Animated, Easing } from 'react-native';
|
||||||
import { HeaderStyleInterpolators, TransitionPresets } from '@react-navigation/stack';
|
import { HeaderStyleInterpolators, TransitionPreset, TransitionPresets } from '@react-navigation/stack';
|
||||||
|
// eslint-disable-next-line import/no-unresolved
|
||||||
|
import { StackCardStyleInterpolator, TransitionSpec } from '@react-navigation/stack/lib/typescript/src/types';
|
||||||
|
|
||||||
import { isAndroid } from '../deviceInfo';
|
import { isAndroid } from '../deviceInfo';
|
||||||
import conditional from './conditional';
|
import conditional from './conditional';
|
||||||
|
|
||||||
const { multiply } = Animated;
|
const { multiply } = Animated;
|
||||||
|
|
||||||
const forFadeFromCenter = ({ current, closing }) => {
|
const forFadeFromCenter: StackCardStyleInterpolator = ({ current, closing }) => {
|
||||||
const opacity = conditional(
|
const opacity = conditional(
|
||||||
closing,
|
closing,
|
||||||
current.progress,
|
current.progress,
|
||||||
|
@ -23,7 +25,7 @@ const forFadeFromCenter = ({ current, closing }) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const FadeIn = {
|
const FadeIn: TransitionSpec = {
|
||||||
animation: 'timing',
|
animation: 'timing',
|
||||||
config: {
|
config: {
|
||||||
duration: 250,
|
duration: 250,
|
||||||
|
@ -31,7 +33,7 @@ const FadeIn = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const FadeOut = {
|
const FadeOut: TransitionSpec = {
|
||||||
animation: 'timing',
|
animation: 'timing',
|
||||||
config: {
|
config: {
|
||||||
duration: 150,
|
duration: 150,
|
||||||
|
@ -48,7 +50,7 @@ export const FadeFromCenterModal = {
|
||||||
cardStyleInterpolator: forFadeFromCenter
|
cardStyleInterpolator: forFadeFromCenter
|
||||||
};
|
};
|
||||||
|
|
||||||
const forStackAndroid = ({ current, inverted, layouts: { screen } }) => {
|
const forStackAndroid: StackCardStyleInterpolator = ({ current, inverted, layouts: { screen } }) => {
|
||||||
const translateX = multiply(
|
const translateX = multiply(
|
||||||
current.progress.interpolate({
|
current.progress.interpolate({
|
||||||
inputRange: [0, 1],
|
inputRange: [0, 1],
|
||||||
|
@ -70,7 +72,7 @@ const forStackAndroid = ({ current, inverted, layouts: { screen } }) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const StackAndroid = {
|
const StackAndroid: TransitionPreset = {
|
||||||
gestureDirection: 'horizontal',
|
gestureDirection: 'horizontal',
|
||||||
transitionSpec: {
|
transitionSpec: {
|
||||||
open: FadeIn,
|
open: FadeIn,
|
|
@ -10,7 +10,11 @@ const { add, multiply } = Animated;
|
||||||
* @param main Animated Node to use if the condition is `true`
|
* @param main Animated Node to use if the condition is `true`
|
||||||
* @param fallback Animated Node to use if the condition is `false`
|
* @param fallback Animated Node to use if the condition is `false`
|
||||||
*/
|
*/
|
||||||
export default function conditional(condition, main, fallback) {
|
export default function conditional(
|
||||||
|
condition: Animated.AnimatedInterpolation,
|
||||||
|
main: Animated.Animated,
|
||||||
|
fallback: Animated.Animated
|
||||||
|
): Animated.AnimatedAddition {
|
||||||
// To implement this behavior, we multiply the main node with the condition.
|
// To implement this behavior, we multiply the main node with the condition.
|
||||||
// So if condition is 0, result will be 0, and if condition is 1, result will be main node.
|
// So if condition is 0, result will be 0, and if condition is 1, result will be main node.
|
||||||
// Then we multiple reverse of the condition (0 if condition is 1) with the fallback.
|
// Then we multiple reverse of the condition (0 if condition is 1) with the fallback.
|
|
@ -14,7 +14,7 @@ const scheme = {
|
||||||
brave: 'brave:'
|
brave: 'brave:'
|
||||||
};
|
};
|
||||||
|
|
||||||
const appSchemeURL = (url, browser) => {
|
const appSchemeURL = (url: string, browser: string): string => {
|
||||||
let schemeUrl = url;
|
let schemeUrl = url;
|
||||||
const parsedUrl = parse(url, true);
|
const parsedUrl = parse(url, true);
|
||||||
const { protocol } = parsedUrl;
|
const { protocol } = parsedUrl;
|
||||||
|
@ -35,7 +35,7 @@ const appSchemeURL = (url, browser) => {
|
||||||
return schemeUrl;
|
return schemeUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
const openLink = async (url, theme = 'light') => {
|
const openLink = async (url: string, theme = 'light'): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY);
|
const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY);
|
||||||
|
|
||||||
|
@ -43,11 +43,12 @@ const openLink = async (url, theme = 'light') => {
|
||||||
await WebBrowser.openBrowserAsync(url, {
|
await WebBrowser.openBrowserAsync(url, {
|
||||||
toolbarColor: themes[theme].headerBackground,
|
toolbarColor: themes[theme].headerBackground,
|
||||||
controlsColor: themes[theme].headerTintColor,
|
controlsColor: themes[theme].headerTintColor,
|
||||||
collapseToolbar: true,
|
// https://github.com/expo/expo/pull/4923
|
||||||
|
enableBarCollapsing: true,
|
||||||
showTitle: true
|
showTitle: true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const schemeUrl = appSchemeURL(url, browser.replace(':', ''));
|
const schemeUrl = appSchemeURL(url, browser!.replace(':', ''));
|
||||||
await Linking.openURL(schemeUrl);
|
await Linking.openURL(schemeUrl);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
|
@ -1,4 +1,4 @@
|
||||||
export default function random(length) {
|
export default function random(length: number): string {
|
||||||
let text = '';
|
let text = '';
|
||||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
for (let i = 0; i < length; i += 1) {
|
for (let i = 0; i < length; i += 1) {
|
|
@ -15,7 +15,7 @@ const reviewDelay = 2000;
|
||||||
const numberOfDays = 7;
|
const numberOfDays = 7;
|
||||||
const numberOfPositiveEvent = 5;
|
const numberOfPositiveEvent = 5;
|
||||||
|
|
||||||
const daysBetween = (date1, date2) => {
|
const daysBetween = (date1: Date, date2: Date): number => {
|
||||||
const one_day = 1000 * 60 * 60 * 24;
|
const one_day = 1000 * 60 * 60 * 24;
|
||||||
const date1_ms = date1.getTime();
|
const date1_ms = date1.getTime();
|
||||||
const date2_ms = date2.getTime();
|
const date2_ms = date2.getTime();
|
||||||
|
@ -32,7 +32,7 @@ const onCancelPress = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onReviewPress = async () => {
|
export const onReviewPress = async (): Promise<void> => {
|
||||||
logEvent(events.SE_REVIEW_THIS_APP);
|
logEvent(events.SE_REVIEW_THIS_APP);
|
||||||
await onCancelPress();
|
await onCancelPress();
|
||||||
try {
|
try {
|
|
@ -1,53 +0,0 @@
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
import { themes } from '../constants/colors';
|
|
||||||
import I18n from '../i18n';
|
|
||||||
|
|
||||||
export const isBlocked = room => {
|
|
||||||
if (room) {
|
|
||||||
const { t, blocked, blocker } = room;
|
|
||||||
if (t === 'd' && (blocked || blocker)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const capitalize = s => {
|
|
||||||
if (typeof s !== 'string') {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatDate = date =>
|
|
||||||
moment(date).calendar(null, {
|
|
||||||
lastDay: `[${I18n.t('Yesterday')}]`,
|
|
||||||
sameDay: 'LT',
|
|
||||||
lastWeek: 'dddd',
|
|
||||||
sameElse: 'L'
|
|
||||||
});
|
|
||||||
|
|
||||||
export const formatDateThreads = date =>
|
|
||||||
moment(date).calendar(null, {
|
|
||||||
sameDay: 'LT',
|
|
||||||
lastDay: `[${I18n.t('Yesterday')}] LT`,
|
|
||||||
lastWeek: 'dddd LT',
|
|
||||||
sameElse: 'LL'
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getBadgeColor = ({ subscription, messageId, theme }) => {
|
|
||||||
if (subscription?.tunreadUser?.includes(messageId)) {
|
|
||||||
return themes[theme].mentionMeColor;
|
|
||||||
}
|
|
||||||
if (subscription?.tunreadGroup?.includes(messageId)) {
|
|
||||||
return themes[theme].mentionGroupColor;
|
|
||||||
}
|
|
||||||
if (subscription?.tunread?.includes(messageId)) {
|
|
||||||
return themes[theme].tunreadColor;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const makeThreadName = messageRecord => messageRecord.msg || messageRecord?.attachments[0]?.title;
|
|
||||||
|
|
||||||
export const isTeamRoom = ({ teamId, joined }) => teamId && joined;
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { themes } from '../constants/colors';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
import { IAttachment } from '../definitions/IAttachment';
|
||||||
|
import { ISubscription, SubscriptionType } from '../definitions/ISubscription';
|
||||||
|
|
||||||
|
export const isBlocked = (room: ISubscription): boolean => {
|
||||||
|
if (room) {
|
||||||
|
const { t, blocked, blocker } = room;
|
||||||
|
if (t === SubscriptionType.DIRECT && (blocked || blocker)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const capitalize = (s: string): string => {
|
||||||
|
if (typeof s !== 'string') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDate = (date: Date): string =>
|
||||||
|
moment(date).calendar(null, {
|
||||||
|
lastDay: `[${I18n.t('Yesterday')}]`,
|
||||||
|
sameDay: 'LT',
|
||||||
|
lastWeek: 'dddd',
|
||||||
|
sameElse: 'L'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const formatDateThreads = (date: Date): string =>
|
||||||
|
moment(date).calendar(null, {
|
||||||
|
sameDay: 'LT',
|
||||||
|
lastDay: `[${I18n.t('Yesterday')}] LT`,
|
||||||
|
lastWeek: 'dddd LT',
|
||||||
|
sameElse: 'LL'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getBadgeColor = ({
|
||||||
|
subscription,
|
||||||
|
messageId,
|
||||||
|
theme
|
||||||
|
}: {
|
||||||
|
// TODO: Refactor when migrate model folder
|
||||||
|
subscription: any;
|
||||||
|
messageId: string;
|
||||||
|
theme: string;
|
||||||
|
}): string | undefined => {
|
||||||
|
if (subscription?.tunreadUser?.includes(messageId)) {
|
||||||
|
return themes[theme].mentionMeColor;
|
||||||
|
}
|
||||||
|
if (subscription?.tunreadGroup?.includes(messageId)) {
|
||||||
|
return themes[theme].mentionGroupColor;
|
||||||
|
}
|
||||||
|
if (subscription?.tunread?.includes(messageId)) {
|
||||||
|
return themes[theme].tunreadColor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeThreadName = (messageRecord: { id?: string; msg?: string; attachments?: IAttachment[] }): string | undefined =>
|
||||||
|
messageRecord.msg || messageRecord.attachments![0].title;
|
||||||
|
|
||||||
|
export const isTeamRoom = ({ teamId, joined }: { teamId: string; joined: boolean }): boolean => !!teamId && joined;
|
|
@ -3,7 +3,7 @@
|
||||||
url = 'https://open.rocket.chat/method'
|
url = 'https://open.rocket.chat/method'
|
||||||
hostname = 'open.rocket.chat'
|
hostname = 'open.rocket.chat'
|
||||||
*/
|
*/
|
||||||
export const extractHostname = url => {
|
export const extractHostname = (url: string): string => {
|
||||||
let hostname;
|
let hostname;
|
||||||
|
|
||||||
if (url.indexOf('//') > -1) {
|
if (url.indexOf('//') > -1) {
|
|
@ -3,7 +3,7 @@
|
||||||
/* eslint-disable object-curly-spacing */
|
/* eslint-disable object-curly-spacing */
|
||||||
/* eslint-disable comma-spacing */
|
/* eslint-disable comma-spacing */
|
||||||
/* eslint-disable key-spacing */
|
/* eslint-disable key-spacing */
|
||||||
const ascii = {
|
const ascii: { [key: string]: string } = {
|
||||||
'*\\0/*': '🙆',
|
'*\\0/*': '🙆',
|
||||||
'*\\O/*': '🙆',
|
'*\\O/*': '🙆',
|
||||||
'-___-': '😑',
|
'-___-': '😑',
|
|
@ -3,7 +3,7 @@
|
||||||
/* eslint-disable object-curly-spacing */
|
/* eslint-disable object-curly-spacing */
|
||||||
/* eslint-disable comma-spacing */
|
/* eslint-disable comma-spacing */
|
||||||
/* eslint-disable key-spacing */
|
/* eslint-disable key-spacing */
|
||||||
const emojis = {
|
const emojis: { [key: string]: string } = {
|
||||||
':england:': '🏴',
|
':england:': '🏴',
|
||||||
':scotland:': '🏴',
|
':scotland:': '🏴',
|
||||||
':wales:': '🏴',
|
':wales:': '🏴',
|
|
@ -2,11 +2,11 @@ import emojis from './emojis';
|
||||||
import ascii, { asciiRegexp } from './ascii';
|
import ascii, { asciiRegexp } from './ascii';
|
||||||
|
|
||||||
const shortnamePattern = new RegExp(/:[-+_a-z0-9]+:/, 'gi');
|
const shortnamePattern = new RegExp(/:[-+_a-z0-9]+:/, 'gi');
|
||||||
const replaceShortNameWithUnicode = shortname => emojis[shortname] || shortname;
|
const replaceShortNameWithUnicode = (shortname: string) => emojis[shortname] || shortname;
|
||||||
const regAscii = new RegExp(`((\\s|^)${asciiRegexp}(?=\\s|$|[!,.?]))`, 'gi');
|
const regAscii = new RegExp(`((\\s|^)${asciiRegexp}(?=\\s|$|[!,.?]))`, 'gi');
|
||||||
|
|
||||||
const unescapeHTML = string => {
|
const unescapeHTML = (string: string) => {
|
||||||
const unescaped = {
|
const unescaped: { [key: string]: string } = {
|
||||||
'&': '&',
|
'&': '&',
|
||||||
'&': '&',
|
'&': '&',
|
||||||
'&': '&',
|
'&': '&',
|
||||||
|
@ -27,7 +27,7 @@ const unescapeHTML = string => {
|
||||||
return string.replace(/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/gi, match => unescaped[match]);
|
return string.replace(/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/gi, match => unescaped[match]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const shortnameToUnicode = str => {
|
const shortnameToUnicode = (str: string): string => {
|
||||||
str = str.replace(shortnamePattern, replaceShortNameWithUnicode);
|
str = str.replace(shortnamePattern, replaceShortNameWithUnicode);
|
||||||
|
|
||||||
str = str.replace(regAscii, (entire, m1, m2, m3) => {
|
str = str.replace(regAscii, (entire, m1, m2, m3) => {
|
|
@ -9,13 +9,18 @@ import { extractHostname } from './server';
|
||||||
const { SSLPinning } = NativeModules;
|
const { SSLPinning } = NativeModules;
|
||||||
const { documentDirectory } = FileSystem;
|
const { documentDirectory } = FileSystem;
|
||||||
|
|
||||||
const extractFileScheme = path => path.replace('file://', ''); // file:// isn't allowed by obj-C
|
const extractFileScheme = (path: string) => path.replace('file://', ''); // file:// isn't allowed by obj-C
|
||||||
|
|
||||||
const getPath = name => `${documentDirectory}/${name}`;
|
const getPath = (name: string) => `${documentDirectory}/${name}`;
|
||||||
|
|
||||||
const persistCertificate = async (name, password) => {
|
interface ICertificate {
|
||||||
|
path: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistCertificate = async (name: string, password: string) => {
|
||||||
const certificatePath = getPath(name);
|
const certificatePath = getPath(name);
|
||||||
const certificate = {
|
const certificate: ICertificate = {
|
||||||
path: extractFileScheme(certificatePath),
|
path: extractFileScheme(certificatePath),
|
||||||
password
|
password
|
||||||
};
|
};
|
||||||
|
@ -29,6 +34,7 @@ const RCSSLPinning = Platform.select({
|
||||||
new Promise(async (resolve, reject) => {
|
new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const res = await DocumentPicker.pick({
|
const res = await DocumentPicker.pick({
|
||||||
|
// @ts-ignore
|
||||||
type: ['com.rsa.pkcs-12']
|
type: ['com.rsa.pkcs-12']
|
||||||
});
|
});
|
||||||
const { uri, name } = res;
|
const { uri, name } = res;
|
||||||
|
@ -42,7 +48,7 @@ const RCSSLPinning = Platform.select({
|
||||||
try {
|
try {
|
||||||
const certificatePath = getPath(name);
|
const certificatePath = getPath(name);
|
||||||
await FileSystem.copyAsync({ from: uri, to: certificatePath });
|
await FileSystem.copyAsync({ from: uri, to: certificatePath });
|
||||||
await persistCertificate(name, password);
|
await persistCertificate(name, password!);
|
||||||
resolve(name);
|
resolve(name);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
|
@ -56,10 +62,10 @@ const RCSSLPinning = Platform.select({
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
setCertificate: async (name, server) => {
|
setCertificate: async (name: string, server: string) => {
|
||||||
if (name) {
|
if (name) {
|
||||||
let certificate = await UserPreferences.getMapAsync(name);
|
let certificate = (await UserPreferences.getMapAsync(name)) as ICertificate;
|
||||||
if (!certificate.path.match(extractFileScheme(documentDirectory))) {
|
if (!certificate.path.match(extractFileScheme(documentDirectory!))) {
|
||||||
certificate = await persistCertificate(name, certificate.password);
|
certificate = await persistCertificate(name, certificate.password);
|
||||||
}
|
}
|
||||||
await UserPreferences.setMapAsync(extractHostname(server), certificate);
|
await UserPreferences.setMapAsync(extractHostname(server), certificate);
|
|
@ -2,12 +2,13 @@ import { Appearance } from 'react-native-appearance';
|
||||||
import changeNavigationBarColor from 'react-native-navigation-bar-color';
|
import changeNavigationBarColor from 'react-native-navigation-bar-color';
|
||||||
import setRootViewColor from 'rn-root-view';
|
import setRootViewColor from 'rn-root-view';
|
||||||
|
|
||||||
|
import { IThemePreference, TThemeMode } from '../definitions/ITheme';
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
import { isAndroid } from './deviceInfo';
|
import { isAndroid } from './deviceInfo';
|
||||||
|
|
||||||
let themeListener;
|
let themeListener: { remove: () => void } | null;
|
||||||
|
|
||||||
export const defaultTheme = () => {
|
export const defaultTheme = (): TThemeMode => {
|
||||||
const systemTheme = Appearance.getColorScheme();
|
const systemTheme = Appearance.getColorScheme();
|
||||||
if (systemTheme && systemTheme !== 'no-preference') {
|
if (systemTheme && systemTheme !== 'no-preference') {
|
||||||
return systemTheme;
|
return systemTheme;
|
||||||
|
@ -15,7 +16,7 @@ export const defaultTheme = () => {
|
||||||
return 'light';
|
return 'light';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTheme = themePreferences => {
|
export const getTheme = (themePreferences: IThemePreference): string => {
|
||||||
const { darkLevel, currentTheme } = themePreferences;
|
const { darkLevel, currentTheme } = themePreferences;
|
||||||
let theme = currentTheme;
|
let theme = currentTheme;
|
||||||
if (currentTheme === 'automatic') {
|
if (currentTheme === 'automatic') {
|
||||||
|
@ -24,7 +25,7 @@ export const getTheme = themePreferences => {
|
||||||
return theme === 'dark' ? darkLevel : 'light';
|
return theme === 'dark' ? darkLevel : 'light';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newThemeState = (prevState, newTheme) => {
|
export const newThemeState = (prevState: { themePreferences: IThemePreference }, newTheme: IThemePreference) => {
|
||||||
// new theme preferences
|
// new theme preferences
|
||||||
const themePreferences = {
|
const themePreferences = {
|
||||||
...prevState.themePreferences,
|
...prevState.themePreferences,
|
||||||
|
@ -35,12 +36,13 @@ export const newThemeState = (prevState, newTheme) => {
|
||||||
return { themePreferences, theme: getTheme(themePreferences) };
|
return { themePreferences, theme: getTheme(themePreferences) };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setNativeTheme = async themePreferences => {
|
export const setNativeTheme = async (themePreferences: IThemePreference): Promise<void> => {
|
||||||
const theme = getTheme(themePreferences);
|
const theme = getTheme(themePreferences);
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
const iconsLight = theme === 'light';
|
const iconsLight = theme === 'light';
|
||||||
try {
|
try {
|
||||||
await changeNavigationBarColor(themes[theme].navbarBackground, iconsLight);
|
// The late param as default is true @ react-native-navigation-bar-color/src/index.js line 8
|
||||||
|
await changeNavigationBarColor(themes[theme].navbarBackground, iconsLight, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
@ -55,7 +57,7 @@ export const unsubscribeTheme = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subscribeTheme = (themePreferences, setTheme) => {
|
export const subscribeTheme = (themePreferences: IThemePreference, setTheme: () => void): void => {
|
||||||
const { currentTheme } = themePreferences;
|
const { currentTheme } = themePreferences;
|
||||||
if (!themeListener && currentTheme === 'automatic') {
|
if (!themeListener && currentTheme === 'automatic') {
|
||||||
// not use listener params because we use getTheme
|
// not use listener params because we use getTheme
|
|
@ -1,26 +0,0 @@
|
||||||
export default function throttle(fn, threshhold = 250, scope) {
|
|
||||||
let last;
|
|
||||||
let deferTimer;
|
|
||||||
|
|
||||||
const _throttle = (...args) => {
|
|
||||||
const context = scope || this;
|
|
||||||
|
|
||||||
const now = +new Date();
|
|
||||||
|
|
||||||
if (last && now < last + threshhold) {
|
|
||||||
// hold on to it
|
|
||||||
clearTimeout(deferTimer);
|
|
||||||
deferTimer = setTimeout(() => {
|
|
||||||
last = now;
|
|
||||||
fn.apply(context, args);
|
|
||||||
}, threshhold);
|
|
||||||
} else {
|
|
||||||
last = now;
|
|
||||||
fn.apply(context, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_throttle.stop = () => clearTimeout(deferTimer);
|
|
||||||
|
|
||||||
return _throttle;
|
|
||||||
}
|
|
|
@ -1,19 +1,27 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { RectButton, RectButtonProps } from 'react-native-gesture-handler';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
|
||||||
|
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
|
|
||||||
class Touch extends React.Component {
|
interface ITouchProps extends RectButtonProps {
|
||||||
setNativeProps(props) {
|
children: React.ReactNode;
|
||||||
|
theme: string;
|
||||||
|
accessibilityLabel?: string;
|
||||||
|
testID?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Touch extends React.Component<ITouchProps> {
|
||||||
|
private ref: any;
|
||||||
|
|
||||||
|
setNativeProps(props: ITouchProps): void {
|
||||||
this.ref.setNativeProps(props);
|
this.ref.setNativeProps(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRef = ref => {
|
getRef = (ref: RectButton): void => {
|
||||||
this.ref = ref;
|
this.ref = ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render(): JSX.Element {
|
||||||
const { children, onPress, theme, underlayColor, ...props } = this.props;
|
const { children, onPress, theme, underlayColor, ...props } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -30,11 +38,4 @@ class Touch extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Touch.propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
onPress: PropTypes.func,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
underlayColor: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Touch;
|
export default Touch;
|
|
@ -3,13 +3,18 @@ import { settings } from '@rocket.chat/sdk';
|
||||||
import { TWO_FACTOR } from '../containers/TwoFactor';
|
import { TWO_FACTOR } from '../containers/TwoFactor';
|
||||||
import EventEmitter from './events';
|
import EventEmitter from './events';
|
||||||
|
|
||||||
export const twoFactor = ({ method, invalid }) =>
|
interface ITwoFactor {
|
||||||
|
method: string;
|
||||||
|
invalid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const twoFactor = ({ method, invalid }: ITwoFactor): Promise<{ twoFactorCode: string; twoFactorMethod: string }> =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
EventEmitter.emit(TWO_FACTOR, {
|
EventEmitter.emit(TWO_FACTOR, {
|
||||||
method,
|
method,
|
||||||
invalid,
|
invalid,
|
||||||
cancel: () => reject(),
|
cancel: () => reject(),
|
||||||
submit: code => {
|
submit: (code: string) => {
|
||||||
settings.customHeaders = {
|
settings.customHeaders = {
|
||||||
...settings.customHeaders,
|
...settings.customHeaders,
|
||||||
'x-2fa-code': code,
|
'x-2fa-code': code,
|
|
@ -1,4 +1,4 @@
|
||||||
export const isValidURL = url => {
|
export const isValidURL = (url: string): boolean => {
|
||||||
const pattern = new RegExp(
|
const pattern = new RegExp(
|
||||||
'^(https?:\\/\\/)?' + // protocol
|
'^(https?:\\/\\/)?' + // protocol
|
||||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
|
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
|
||||||
|
@ -12,4 +12,4 @@ export const isValidURL = url => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use useSsl: false only if server url starts with http://
|
// Use useSsl: false only if server url starts with http://
|
||||||
export const useSsl = url => !/http:\/\//.test(url);
|
export const useSsl = (url: string): boolean => !/http:\/\//.test(url);
|
|
@ -32,8 +32,6 @@ const SelectChannel = ({
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const getAvatar = (item: any) =>
|
const getAvatar = (item: any) =>
|
||||||
// TODO: remove this ts-ignore when migrate the file: app/utils/avatar.js
|
|
||||||
// @ts-ignore
|
|
||||||
avatarURL({
|
avatarURL({
|
||||||
text: RocketChat.getRoomAvatar(item),
|
text: RocketChat.getRoomAvatar(item),
|
||||||
type: item.t,
|
type: item.t,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { ICreateDiscussionViewSelectUsers } from './interfaces';
|
import { ICreateDiscussionViewSelectUsers } from './interfaces';
|
||||||
|
import { SubscriptionType } from '../../definitions/ISubscription';
|
||||||
|
|
||||||
interface IUser {
|
interface IUser {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -62,11 +63,9 @@ const SelectUsers = ({
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const getAvatar = (item: any) =>
|
const getAvatar = (item: any) =>
|
||||||
// TODO: remove this ts-ignore when migrate the file: app/utils/avatar.js
|
|
||||||
// @ts-ignore
|
|
||||||
avatarURL({
|
avatarURL({
|
||||||
text: RocketChat.getRoomAvatar(item),
|
text: RocketChat.getRoomAvatar(item),
|
||||||
type: 'd',
|
type: SubscriptionType.DIRECT,
|
||||||
user: { id: userId, token },
|
user: { id: userId, token },
|
||||||
server,
|
server,
|
||||||
avatarETag: item.avatarETag,
|
avatarETag: item.avatarETag,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { RouteProp } from '@react-navigation/core';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
|
||||||
import { NewMessageStackParamList } from '../../stacks/types';
|
import { NewMessageStackParamList } from '../../stacks/types';
|
||||||
|
import { SubscriptionType } from '../../definitions/ISubscription';
|
||||||
|
|
||||||
export interface ICreateChannelViewProps {
|
export interface ICreateChannelViewProps {
|
||||||
navigation: StackNavigationProp<NewMessageStackParamList, 'CreateDiscussionView'>;
|
navigation: StackNavigationProp<NewMessageStackParamList, 'CreateDiscussionView'>;
|
||||||
|
@ -15,7 +16,7 @@ export interface ICreateChannelViewProps {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
result: {
|
result: {
|
||||||
rid: string;
|
rid: string;
|
||||||
t: string;
|
t: SubscriptionType;
|
||||||
prid: string;
|
prid: string;
|
||||||
};
|
};
|
||||||
failure: boolean;
|
failure: boolean;
|
||||||
|
|
|
@ -75,8 +75,6 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
|
||||||
if (!newPassword.trim()) {
|
if (!newPassword.trim()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
|
|
||||||
// @ts-ignore
|
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
title: I18n.t('Are_you_sure_question_mark'),
|
title: I18n.t('Are_you_sure_question_mark'),
|
||||||
message: I18n.t('E2E_encryption_change_password_message'),
|
message: I18n.t('E2E_encryption_change_password_message'),
|
||||||
|
@ -98,8 +96,6 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
|
||||||
};
|
};
|
||||||
|
|
||||||
resetOwnKey = () => {
|
resetOwnKey = () => {
|
||||||
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
|
|
||||||
// @ts-ignore
|
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
title: I18n.t('Are_you_sure_question_mark'),
|
title: I18n.t('Are_you_sure_question_mark'),
|
||||||
message: I18n.t('E2E_encryption_reset_message'),
|
message: I18n.t('E2E_encryption_reset_message'),
|
||||||
|
|
|
@ -269,11 +269,10 @@ class NewServerView extends React.Component<INewServerView, IState> {
|
||||||
uriToPath = (uri: string) => uri.replace('file://', '');
|
uriToPath = (uri: string) => uri.replace('file://', '');
|
||||||
|
|
||||||
handleRemove = () => {
|
handleRemove = () => {
|
||||||
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
|
|
||||||
// @ts-ignore
|
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
message: I18n.t('You_will_unset_a_certificate_for_this_server'),
|
message: I18n.t('You_will_unset_a_certificate_for_this_server'),
|
||||||
confirmationText: I18n.t('Remove'),
|
confirmationText: I18n.t('Remove'),
|
||||||
|
// @ts-ignore
|
||||||
onPress: this.setState({ certificate: null }) // We not need delete file from DocumentPicker because it is a temp file
|
onPress: this.setState({ certificate: null }) // We not need delete file from DocumentPicker because it is a temp file
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -424,7 +424,6 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
||||||
|
|
||||||
logoutOtherLocations = () => {
|
logoutOtherLocations = () => {
|
||||||
logEvent(events.PL_OTHER_LOCATIONS);
|
logEvent(events.PL_OTHER_LOCATIONS);
|
||||||
// @ts-ignore
|
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
message: I18n.t('You_will_be_logged_out_from_other_locations'),
|
message: I18n.t('You_will_be_logged_out_from_other_locations'),
|
||||||
confirmationText: I18n.t('Logout'),
|
confirmationText: I18n.t('Logout'),
|
||||||
|
|
|
@ -26,9 +26,9 @@ export interface IParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAvatarButton {
|
export interface IAvatarButton {
|
||||||
key: React.Key;
|
key: string;
|
||||||
child: React.ReactNode;
|
child: React.ReactNode;
|
||||||
onPress: Function;
|
onPress: () => void;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||||
import { Switch } from 'react-native';
|
import { Switch } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||||
import Model from '@nozbe/watermelondb/Model';
|
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
@ -15,15 +14,10 @@ import { changePasscode, checkHasPasscode, supportedBiometryLabel } from '../uti
|
||||||
import { DEFAULT_AUTO_LOCK } from '../constants/localAuthentication';
|
import { DEFAULT_AUTO_LOCK } from '../constants/localAuthentication';
|
||||||
import SafeAreaView from '../containers/SafeAreaView';
|
import SafeAreaView from '../containers/SafeAreaView';
|
||||||
import { events, logEvent } from '../utils/log';
|
import { events, logEvent } from '../utils/log';
|
||||||
|
import { TServerModel } from '../definitions/IServer';
|
||||||
|
|
||||||
const DEFAULT_BIOMETRY = false;
|
const DEFAULT_BIOMETRY = false;
|
||||||
|
|
||||||
interface IServerRecords extends Model {
|
|
||||||
autoLock?: boolean;
|
|
||||||
autoLockTime?: number;
|
|
||||||
biometry?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IItem {
|
interface IItem {
|
||||||
title: string;
|
title: string;
|
||||||
value: number;
|
value: number;
|
||||||
|
@ -38,14 +32,14 @@ interface IScreenLockConfigViewProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IScreenLockConfigViewState {
|
interface IScreenLockConfigViewState {
|
||||||
autoLock?: boolean;
|
autoLock: boolean;
|
||||||
autoLockTime?: number | null;
|
autoLockTime?: number | null;
|
||||||
biometry?: boolean;
|
biometry?: boolean;
|
||||||
biometryLabel: null;
|
biometryLabel: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, IScreenLockConfigViewState> {
|
class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, IScreenLockConfigViewState> {
|
||||||
private serverRecord?: IServerRecords;
|
private serverRecord?: TServerModel;
|
||||||
|
|
||||||
private observable?: Subscription;
|
private observable?: Subscription;
|
||||||
|
|
||||||
|
@ -98,7 +92,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
const serversCollection = serversDB.get('servers');
|
const serversCollection = serversDB.get('servers');
|
||||||
try {
|
try {
|
||||||
this.serverRecord = await serversCollection.find(server);
|
this.serverRecord = (await serversCollection.find(server)) as TServerModel;
|
||||||
this.setState({
|
this.setState({
|
||||||
autoLock: this.serverRecord?.autoLock,
|
autoLock: this.serverRecord?.autoLock,
|
||||||
autoLockTime: this.serverRecord?.autoLockTime === null ? DEFAULT_AUTO_LOCK : this.serverRecord?.autoLockTime,
|
autoLockTime: this.serverRecord?.autoLockTime === null ? DEFAULT_AUTO_LOCK : this.serverRecord?.autoLockTime,
|
||||||
|
@ -151,7 +145,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
|
||||||
const { autoLock } = this.state;
|
const { autoLock } = this.state;
|
||||||
if (autoLock) {
|
if (autoLock) {
|
||||||
try {
|
try {
|
||||||
await checkHasPasscode({ force: false, serverRecord: this.serverRecord });
|
await checkHasPasscode({ force: false, serverRecord: this.serverRecord! });
|
||||||
} catch {
|
} catch {
|
||||||
this.toggleAutoLock();
|
this.toggleAutoLock();
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,6 @@ class SettingsView extends React.Component<ISettingsViewProps, any> {
|
||||||
|
|
||||||
handleLogout = () => {
|
handleLogout = () => {
|
||||||
logEvent(events.SE_LOG_OUT);
|
logEvent(events.SE_LOG_OUT);
|
||||||
// @ts-ignore
|
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
message: I18n.t('You_will_be_logged_out_of_this_application'),
|
message: I18n.t('You_will_be_logged_out_of_this_application'),
|
||||||
confirmationText: I18n.t('Logout'),
|
confirmationText: I18n.t('Logout'),
|
||||||
|
@ -98,7 +97,6 @@ class SettingsView extends React.Component<ISettingsViewProps, any> {
|
||||||
|
|
||||||
handleClearCache = () => {
|
handleClearCache = () => {
|
||||||
logEvent(events.SE_CLEAR_LOCAL_SERVER_CACHE);
|
logEvent(events.SE_CLEAR_LOCAL_SERVER_CACHE);
|
||||||
/* @ts-ignore */
|
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
message: I18n.t('This_will_clear_all_your_offline_data'),
|
message: I18n.t('This_will_clear_all_your_offline_data'),
|
||||||
confirmationText: I18n.t('Clear'),
|
confirmationText: I18n.t('Clear'),
|
||||||
|
|
|
@ -25,12 +25,12 @@ import { sanitizeLikeString } from '../../lib/database/utils';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import ShareListHeader from './Header';
|
import ShareListHeader from './Header';
|
||||||
|
|
||||||
interface IFile {
|
interface IDataFromShare {
|
||||||
value: string;
|
value: string;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAttachment {
|
interface IFileToShare {
|
||||||
filename: string;
|
filename: string;
|
||||||
description: string;
|
description: string;
|
||||||
size: number;
|
size: number;
|
||||||
|
@ -61,7 +61,7 @@ interface IState {
|
||||||
searchResults: IChat[];
|
searchResults: IChat[];
|
||||||
chats: IChat[];
|
chats: IChat[];
|
||||||
serversCount: number;
|
serversCount: number;
|
||||||
attachments: IAttachment[];
|
attachments: IFileToShare[];
|
||||||
text: string;
|
text: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
serverInfo: IServerInfo;
|
serverInfo: IServerInfo;
|
||||||
|
@ -121,7 +121,7 @@ class ShareListView extends React.Component<IShareListViewProps, IState> {
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const { server } = this.props;
|
const { server } = this.props;
|
||||||
try {
|
try {
|
||||||
const data = (await ShareExtension.data()) as IFile[];
|
const data = (await ShareExtension.data()) as IDataFromShare[];
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
await this.askForPermission(data);
|
await this.askForPermission(data);
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ class ShareListView extends React.Component<IShareListViewProps, IState> {
|
||||||
size: file.size,
|
size: file.size,
|
||||||
mime: mime.lookup(file.uri),
|
mime: mime.lookup(file.uri),
|
||||||
path: file.uri
|
path: file.uri
|
||||||
})) as IAttachment[];
|
})) as IFileToShare[];
|
||||||
const text = data.filter(item => item.type === 'text').reduce((acc, item) => `${item.value}\n${acc}`, '');
|
const text = data.filter(item => item.type === 'text').reduce((acc, item) => `${item.value}\n${acc}`, '');
|
||||||
this.setState({
|
this.setState({
|
||||||
text,
|
text,
|
||||||
|
@ -294,7 +294,7 @@ class ShareListView extends React.Component<IShareListViewProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
askForPermission = async (data: IFile[]) => {
|
askForPermission = async (data: IDataFromShare[]) => {
|
||||||
const mediaIndex = data.findIndex(item => item.type === 'media');
|
const mediaIndex = data.findIndex(item => item.type === 'media');
|
||||||
if (mediaIndex !== -1) {
|
if (mediaIndex !== -1) {
|
||||||
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, permission);
|
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, permission);
|
||||||
|
|
|
@ -39,7 +39,7 @@ interface IShareViewState {
|
||||||
room: ISubscription;
|
room: ISubscription;
|
||||||
thread: any; // change
|
thread: any; // change
|
||||||
maxFileSize: number;
|
maxFileSize: number;
|
||||||
mediaAllowList: number;
|
mediaAllowList: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IShareViewProps {
|
interface IShareViewProps {
|
||||||
|
@ -52,7 +52,7 @@ interface IShareViewProps {
|
||||||
token: string;
|
token: string;
|
||||||
};
|
};
|
||||||
server: string;
|
server: string;
|
||||||
FileUpload_MediaTypeWhiteList?: number;
|
FileUpload_MediaTypeWhiteList?: string;
|
||||||
FileUpload_MaxFileSize?: number;
|
FileUpload_MaxFileSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,17 +11,18 @@ import { supportSystemTheme } from '../utils/deviceInfo';
|
||||||
import SafeAreaView from '../containers/SafeAreaView';
|
import SafeAreaView from '../containers/SafeAreaView';
|
||||||
import UserPreferences from '../lib/userPreferences';
|
import UserPreferences from '../lib/userPreferences';
|
||||||
import { events, logEvent } from '../utils/log';
|
import { events, logEvent } from '../utils/log';
|
||||||
|
import { IThemePreference, TThemeMode, TDarkLevel } from '../definitions/ITheme';
|
||||||
|
|
||||||
const THEME_GROUP = 'THEME_GROUP';
|
const THEME_GROUP = 'THEME_GROUP';
|
||||||
const DARK_GROUP = 'DARK_GROUP';
|
const DARK_GROUP = 'DARK_GROUP';
|
||||||
|
|
||||||
const SYSTEM_THEME = {
|
const SYSTEM_THEME: ITheme = {
|
||||||
label: 'Automatic',
|
label: 'Automatic',
|
||||||
value: 'automatic',
|
value: 'automatic',
|
||||||
group: THEME_GROUP
|
group: THEME_GROUP
|
||||||
};
|
};
|
||||||
|
|
||||||
const THEMES = [
|
const THEMES: ITheme[] = [
|
||||||
{
|
{
|
||||||
label: 'Light',
|
label: 'Light',
|
||||||
value: 'light',
|
value: 'light',
|
||||||
|
@ -53,15 +54,10 @@ const darkGroup = THEMES.filter(item => item.group === DARK_GROUP);
|
||||||
|
|
||||||
interface ITheme {
|
interface ITheme {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: TThemeMode | TDarkLevel;
|
||||||
group: string;
|
group: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IThemePreference {
|
|
||||||
currentTheme?: string;
|
|
||||||
darkLevel?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IThemeViewProps {
|
interface IThemeViewProps {
|
||||||
theme: string;
|
theme: string;
|
||||||
themePreferences: IThemePreference;
|
themePreferences: IThemePreference;
|
||||||
|
@ -89,19 +85,19 @@ class ThemeView extends React.Component<IThemeViewProps> {
|
||||||
const { themePreferences } = this.props;
|
const { themePreferences } = this.props;
|
||||||
const { darkLevel, currentTheme } = themePreferences;
|
const { darkLevel, currentTheme } = themePreferences;
|
||||||
const { value, group } = item;
|
const { value, group } = item;
|
||||||
let changes: IThemePreference = {};
|
let changes: Partial<IThemePreference> = {};
|
||||||
if (group === THEME_GROUP && currentTheme !== value) {
|
if (group === THEME_GROUP && currentTheme !== value) {
|
||||||
logEvent(events.THEME_SET_THEME_GROUP, { theme_group: value });
|
logEvent(events.THEME_SET_THEME_GROUP, { theme_group: value });
|
||||||
changes = { currentTheme: value };
|
changes = { currentTheme: value as TThemeMode };
|
||||||
}
|
}
|
||||||
if (group === DARK_GROUP && darkLevel !== value) {
|
if (group === DARK_GROUP && darkLevel !== value) {
|
||||||
logEvent(events.THEME_SET_DARK_LEVEL, { dark_level: value });
|
logEvent(events.THEME_SET_DARK_LEVEL, { dark_level: value });
|
||||||
changes = { darkLevel: value };
|
changes = { darkLevel: value as TDarkLevel };
|
||||||
}
|
}
|
||||||
this.setTheme(changes);
|
this.setTheme(changes);
|
||||||
};
|
};
|
||||||
|
|
||||||
setTheme = async (theme: IThemePreference) => {
|
setTheme = async (theme: Partial<IThemePreference>) => {
|
||||||
const { setTheme, themePreferences } = this.props;
|
const { setTheme, themePreferences } = this.props;
|
||||||
const newTheme = { ...themePreferences, ...theme };
|
const newTheme = { ...themePreferences, ...theme };
|
||||||
setTheme(newTheme);
|
setTheme(newTheme);
|
||||||
|
|
Loading…
Reference in New Issue