Chore: Migrate Utils Folder to Typescript (#3544)

Co-authored-by: AlexAlexandre <alexalexandrejr@gmail.com>
This commit is contained in:
Reinaldo Neto 2022-01-12 09:54:04 -03:00 committed by GitHub
parent f7418791a2
commit a2b92f5e70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 458 additions and 353 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -305,8 +305,6 @@ const MessageActions = React.memo(
}; };
const handleDelete = (message: any) => { const handleDelete = (message: any) => {
// TODO - migrate this function for ts when fix the lint erros
// @ts-ignore
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('You_will_not_be_able_to_recover_this_message'), message: I18n.t('You_will_not_be_able_to_recover_this_message'),
confirmationText: I18n.t('Delete'), confirmationText: I18n.t('Delete'),

View File

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

View File

@ -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;
} }

View File

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

View File

@ -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;

View File

@ -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>;

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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;
} }

View File

@ -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 : ''
}; };

View File

@ -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}`;

View File

@ -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

View File

@ -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;
}

22
app/utils/debounce.ts Normal file
View File

@ -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;
}

View File

@ -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);
}; };

View File

@ -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) {

View File

@ -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 };
} }

View File

@ -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

View File

@ -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 {

View File

@ -0,0 +1,7 @@
export interface IFileUpload {
name: string;
uri?: string;
type: string;
filename: string;
data: any;
}

View File

@ -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

View File

@ -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 }
);

41
app/utils/info.ts Normal file
View File

@ -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 }
);

View File

@ -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;
} }

View File

@ -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,}))$/;

View File

@ -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();

View File

@ -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) {

View File

@ -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 };
} }

View File

@ -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];

View File

@ -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,

View File

@ -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.

View File

@ -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 {

View File

@ -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) {

View File

@ -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 {

View File

@ -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;

65
app/utils/room.ts Normal file
View File

@ -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;

View File

@ -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) {

View File

@ -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/*': '🙆',
'-___-': '😑', '-___-': '😑',

View File

@ -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:': '🏴󠁧󠁢󠁷󠁬󠁳󠁿',

View File

@ -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 } = {
'&amp;': '&', '&amp;': '&',
'&#38;': '&', '&#38;': '&',
'&#x26;': '&', '&#x26;': '&',
@ -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) => {

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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;

View File

@ -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'),

View File

@ -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
}); });
}; };

View 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'),

View File

@ -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;
} }

View File

@ -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();
} }

View File

@ -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'),

View File

@ -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);

View File

@ -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;
} }

View File

@ -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);