diff --git a/app/containers/ActionSheet/Button.ts b/app/containers/ActionSheet/Button.ts index 5deb0f692..186cc6851 100644 --- a/app/containers/ActionSheet/Button.ts +++ b/app/containers/ActionSheet/Button.ts @@ -1,7 +1,8 @@ +import React from 'react'; import { TouchableOpacity } from 'react-native'; import { isAndroid } from '../../utils/deviceInfo'; import Touch from '../../utils/touch'; // 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; diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx index 286bcc060..0ad2634f2 100644 --- a/app/containers/Avatar/Avatar.tsx +++ b/app/containers/Avatar/Avatar.tsx @@ -5,6 +5,7 @@ import Touchable from 'react-native-platform-touchable'; import { settings as RocketChatSettings } from '@rocket.chat/sdk'; import { avatarURL } from '../../utils/avatar'; +import { SubscriptionType } from '../../definitions/ISubscription'; import Emoji from '../markdown/Emoji'; import { IAvatar } from './interfaces'; @@ -27,8 +28,8 @@ const Avatar = React.memo( text, size = 25, borderRadius = 4, - type = 'd' - }: Partial) => { + type = SubscriptionType.DIRECT + }: IAvatar) => { if ((!text && !avatar && !emoji && !rid) || !server) { return null; } diff --git a/app/containers/Avatar/index.tsx b/app/containers/Avatar/index.tsx index 9c95db839..2ce066479 100644 --- a/app/containers/Avatar/index.tsx +++ b/app/containers/Avatar/index.tsx @@ -7,17 +7,17 @@ import { getUserSelector } from '../../selectors/login'; import Avatar from './Avatar'; import { IAvatar } from './interfaces'; -class AvatarContainer extends React.Component, any> { +class AvatarContainer extends React.Component { private mounted: boolean; - private subscription!: any; + private subscription: any; static defaultProps = { text: '', type: 'd' }; - constructor(props: Partial) { + constructor(props: IAvatar) { super(props); this.mounted = false; this.state = { avatarETag: '' }; @@ -55,7 +55,7 @@ class AvatarContainer extends React.Component, any> { try { if (this.isDirect) { 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; } else { const { rid } = this.props; @@ -82,7 +82,7 @@ class AvatarContainer extends React.Component, any> { render() { const { avatarETag } = this.state; const { serverVersion } = this.props; - return ; + return ; } } diff --git a/app/containers/Avatar/interfaces.ts b/app/containers/Avatar/interfaces.ts index ed7fd3b9e..78152e522 100644 --- a/app/containers/Avatar/interfaces.ts +++ b/app/containers/Avatar/interfaces.ts @@ -1,23 +1,23 @@ export interface IAvatar { - server: string; - style: any; + server?: string; + style?: any; text: string; - avatar: string; - emoji: string; - size: number; - borderRadius: number; - type: string; - children: JSX.Element; - user: { - id: string; - token: string; + avatar?: string; + emoji?: string; + size?: number; + borderRadius?: number; + type?: string; + children?: JSX.Element; + user?: { + id?: string; + token?: string; }; - theme: string; - onPress(): void; - getCustomEmoji(): any; - avatarETag: string; - isStatic: boolean | string; - rid: string; - blockUnauthenticatedAccess: boolean; - serverVersion: string; + theme?: string; + onPress?: () => void; + getCustomEmoji?: () => any; + avatarETag?: string; + isStatic?: boolean | string; + rid?: string; + blockUnauthenticatedAccess?: boolean; + serverVersion?: string; } diff --git a/app/containers/MessageActions/index.tsx b/app/containers/MessageActions/index.tsx index eb9be9675..4d32abf3b 100644 --- a/app/containers/MessageActions/index.tsx +++ b/app/containers/MessageActions/index.tsx @@ -305,8 +305,6 @@ const MessageActions = React.memo( }; const handleDelete = (message: any) => { - // TODO - migrate this function for ts when fix the lint erros - // @ts-ignore showConfirmationAlert({ message: I18n.t('You_will_not_be_able_to_recover_this_message'), confirmationText: I18n.t('Delete'), diff --git a/app/containers/Passcode/Base/Button.tsx b/app/containers/Passcode/Base/Button.tsx index f7e6c1a9a..50a1cf417 100644 --- a/app/containers/Passcode/Base/Button.tsx +++ b/app/containers/Passcode/Base/Button.tsx @@ -7,28 +7,28 @@ import Touch from '../../../utils/touch'; import { CustomIcon } from '../../../lib/Icons'; interface IPasscodeButton { - text: string; - icon: string; + text?: string; + icon?: string; theme: string; - disabled: boolean; - onPress: Function; + disabled?: boolean; + onPress?: Function; } -const Button = React.memo(({ text, disabled, theme, onPress, icon }: Partial) => { - const press = () => onPress && onPress(text!); +const Button = React.memo(({ text, disabled, theme, onPress, icon }: IPasscodeButton) => { + const press = () => onPress && onPress(text); return ( {icon ? ( - + ) : ( - {text} + {text} )} ); diff --git a/app/containers/message/interfaces.ts b/app/containers/message/interfaces.ts index c9be40af9..866e7e40e 100644 --- a/app/containers/message/interfaces.ts +++ b/app/containers/message/interfaces.ts @@ -84,7 +84,7 @@ export interface IMessageContent { export interface IMessageDiscussion { msg: string; dcount: number; - dlm: string; + dlm: Date; theme: string; } diff --git a/app/definitions/ICommand.ts b/app/definitions/ICommand.ts new file mode 100644 index 000000000..a0abeec26 --- /dev/null +++ b/app/definitions/ICommand.ts @@ -0,0 +1,6 @@ +export interface ICommand { + event: { + input: string; + modifierFlags: number; + }; +} diff --git a/app/definitions/IServer.ts b/app/definitions/IServer.ts index 014a2702b..0c3bf57d3 100644 --- a/app/definitions/IServer.ts +++ b/app/definitions/IServer.ts @@ -10,8 +10,8 @@ export interface IServer { version: string; lastLocalAuthenticatedSession: Date; autoLock: boolean; - autoLockTime: number | null; - biometry: boolean | null; + autoLockTime?: number; + biometry?: boolean; uniqueID: string; enterpriseModules: string; E2E_Enable: boolean; diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts index 5f561edfb..1f241599a 100644 --- a/app/definitions/ISubscription.ts +++ b/app/definitions/ISubscription.ts @@ -79,6 +79,8 @@ export interface ISubscription { avatarETag?: string; teamId?: string; teamMain?: boolean; + search?: boolean; + username?: string; // https://nozbe.github.io/WatermelonDB/Relation.html#relation-api messages: Relation; threads: Relation; diff --git a/app/definitions/ITheme.ts b/app/definitions/ITheme.ts new file mode 100644 index 000000000..208a0b2d2 --- /dev/null +++ b/app/definitions/ITheme.ts @@ -0,0 +1,8 @@ +export type TThemeMode = 'automatic' | 'light' | 'dark'; + +export type TDarkLevel = 'black' | 'dark'; + +export interface IThemePreference { + currentTheme: TThemeMode; + darkLevel: TDarkLevel; +} diff --git a/app/externalModules.d.ts b/app/externalModules.d.ts index f68cb5e39..02c57204f 100644 --- a/app/externalModules.d.ts +++ b/app/externalModules.d.ts @@ -13,3 +13,4 @@ declare module 'react-native-mime-types'; declare module 'react-native-restart'; declare module 'react-native-prompt-android'; declare module 'react-native-jitsi-meet'; +declare module 'rn-root-view'; diff --git a/app/index.tsx b/app/index.tsx index e6457e233..cc0a06a26 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -30,6 +30,8 @@ import InAppNotification from './containers/InAppNotification'; import { ActionSheetProvider } from './containers/ActionSheet'; import debounce from './utils/debounce'; import { isFDroidBuild } from './constants/environment'; +import { IThemePreference } from './definitions/ITheme'; +import { ICommand } from './definitions/ICommand'; RNScreens.enableScreens(); @@ -42,10 +44,7 @@ interface IDimensions { interface IState { theme: string; - themePreferences: { - currentTheme: 'automatic' | 'light'; - darkLevel: string; - }; + themePreferences: IThemePreference; width: number; height: number; scale: number; @@ -175,7 +174,7 @@ export default class Root extends React.Component<{}, IState> { setTheme = (newTheme = {}) => { // change theme state this.setState( - prevState => newThemeState(prevState, newTheme), + prevState => newThemeState(prevState, newTheme as IThemePreference), () => { const { themePreferences } = this.state; // subscribe to Appearance changes @@ -191,7 +190,7 @@ export default class Root extends React.Component<{}, IState> { initTablet = () => { const { width } = this.state; this.setMasterDetail(width); - this.onKeyCommands = KeyCommandsEmitter.addListener('onKeyCommand', (command: unknown) => { + this.onKeyCommands = KeyCommandsEmitter.addListener('onKeyCommand', (command: ICommand) => { EventEmitter.emit(KEY_COMMAND, { event: command }); }); }; diff --git a/app/share.tsx b/app/share.tsx index daee2fba0..fbfcd0b52 100644 --- a/app/share.tsx +++ b/app/share.tsx @@ -14,6 +14,7 @@ import { defaultHeader, getActiveRouteName, navigationTheme, themedHeader } from import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat'; import { ThemeContext } from './theme'; import { localAuthenticate } from './utils/localAuthentication'; +import { IThemePreference } from './definitions/ITheme'; import ScreenLockedView from './views/ScreenLockedView'; // Outside Stack import WithoutServersView from './views/WithoutServersView'; @@ -36,10 +37,7 @@ interface IDimensions { interface IState { theme: string; - themePreferences: { - currentTheme: 'automatic' | 'light'; - darkLevel: string; - }; + themePreferences: IThemePreference; root: any; width: number; height: number; @@ -135,7 +133,7 @@ class Root extends React.Component<{}, IState> { setTheme = (newTheme = {}) => { // change theme state this.setState( - prevState => newThemeState(prevState, newTheme), + prevState => newThemeState(prevState, newTheme as IThemePreference), () => { const { themePreferences } = this.state; // subscribe to Appearance changes diff --git a/app/theme.tsx b/app/theme.tsx index 635ffda1c..9959cd768 100644 --- a/app/theme.tsx +++ b/app/theme.tsx @@ -1,12 +1,11 @@ import React from 'react'; import hoistNonReactStatics from 'hoist-non-react-statics'; +import { IThemePreference } from './definitions/ITheme'; + interface IThemeContextProps { theme: string; - themePreferences?: { - currentTheme: 'automatic' | 'light'; - darkLevel: string; - }; + themePreferences?: IThemePreference; setTheme?: (newTheme?: {}) => void; } diff --git a/app/utils/appGroup.js b/app/utils/appGroup.ts similarity index 83% rename from app/utils/appGroup.js rename to app/utils/appGroup.ts index 63fb428aa..f92227c03 100644 --- a/app/utils/appGroup.js +++ b/app/utils/appGroup.ts @@ -4,7 +4,7 @@ import { isIOS } from './deviceInfo'; const { AppGroup } = NativeModules; -const appGroup = { +const appGroup: { path: string } = { path: isIOS ? AppGroup.path : '' }; diff --git a/app/utils/avatar.js b/app/utils/avatar.ts similarity index 72% rename from app/utils/avatar.js rename to app/utils/avatar.ts index 4cc15cdad..7e4b28195 100644 --- a/app/utils/avatar.js +++ b/app/utils/avatar.ts @@ -1,6 +1,8 @@ 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 = ({ type, @@ -13,9 +15,9 @@ export const avatarURL = ({ rid, blockUnauthenticatedAccess, serverVersion -}) => { +}: IAvatar): string => { let room; - if (type === 'd') { + if (type === SubscriptionType.DIRECT) { room = text; } else if (rid && !compareServerVersion(serverVersion, '3.6.0', methods.lowerThan)) { room = `room/${rid}`; diff --git a/app/utils/base64-js/index.js b/app/utils/base64-js/index.ts similarity index 87% rename from app/utils/base64-js/index.js rename to app/utils/base64-js/index.ts index 5616f71df..71fac91ce 100644 --- a/app/utils/base64-js/index.js +++ b/app/utils/base64-js/index.ts @@ -1,8 +1,8 @@ /* eslint-disable no-bitwise */ // https://github.com/beatgammit/base64-js/blob/master/index.js -const lookup = []; -const revLookup = []; +const lookup: string[] = []; +const revLookup: number[] = []; const Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array; 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)] = 63; -const getLens = b64 => { +const getLens = (b64: string) => { const len = b64.length; // 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 -export const byteLength = b64 => { +export const byteLength = (b64: string) => { const lens = getLens(b64); const validLen = lens[0]; const placeHoldersLen = lens[1]; 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; const lens = getLens(b64); const validLen = lens[0]; @@ -92,10 +93,10 @@ export const toByteArray = b64 => { return arr; }; -const tripletToBase64 = num => +const tripletToBase64 = (num: number) => 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; const output = []; for (let i = start; i < end; i += 3) { @@ -105,7 +106,7 @@ const encodeChunk = (uint8, start, end) => { return output.join(''); }; -export const fromByteArray = uint8 => { +export const fromByteArray = (uint8: number[] | Uint8Array) => { let tmp; const len = uint8.length; const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes diff --git a/app/utils/debounce.js b/app/utils/debounce.js deleted file mode 100644 index 106c61d00..000000000 --- a/app/utils/debounce.js +++ /dev/null @@ -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; -} diff --git a/app/utils/debounce.ts b/app/utils/debounce.ts new file mode 100644 index 000000000..e0c28b239 --- /dev/null +++ b/app/utils/debounce.ts @@ -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; +} diff --git a/app/utils/deviceInfo.js b/app/utils/deviceInfo.ts similarity index 92% rename from app/utils/deviceInfo.js rename to app/utils/deviceInfo.ts index 7961b440a..9cb7ac727 100644 --- a/app/utils/deviceInfo.js +++ b/app/utils/deviceInfo.ts @@ -9,7 +9,7 @@ export const getBundleId = DeviceInfo.getBundleId(); export const getDeviceModel = DeviceInfo.getModel(); // Theme is supported by system on iOS 13+ or Android 10+ -export const supportSystemTheme = () => { +export const supportSystemTheme = (): boolean => { const systemVersion = parseInt(DeviceInfo.getSystemVersion(), 10); return systemVersion >= (isIOS ? 13 : 10); }; diff --git a/app/utils/events.js b/app/utils/events.ts similarity index 53% rename from app/utils/events.js rename to app/utils/events.ts index 8e67fc82e..fc0b975ad 100644 --- a/app/utils/events.js +++ b/app/utils/events.ts @@ -1,11 +1,25 @@ +import { ICommand } from '../definitions/ICommand'; 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 { + private events: { [key: string]: any }; + constructor() { this.events = {}; } - addEventListener(event, listener) { + addEventListener(event: string, listener: Function) { if (typeof this.events[event] !== 'object') { this.events[event] = []; } @@ -13,7 +27,7 @@ class EventEmitter { return listener; } - removeListener(event, listener) { + removeListener(event: string, listener: Function) { if (typeof this.events[event] === 'object') { const idx = this.events[event].indexOf(listener); if (idx > -1) { @@ -25,9 +39,9 @@ class EventEmitter { } } - emit(event, ...args) { + emit(event: string, ...args: TEventEmitterEmmitArgs[]) { if (typeof this.events[event] === 'object') { - this.events[event].forEach(listener => { + this.events[event].forEach((listener: Function) => { try { listener.apply(this, args); } catch (e) { diff --git a/app/utils/fetch.js b/app/utils/fetch.ts similarity index 75% rename from app/utils/fetch.js rename to app/utils/fetch.ts index 84f5669ae..c8758da8b 100644 --- a/app/utils/fetch.js +++ b/app/utils/fetch.ts @@ -4,15 +4,20 @@ import { settings as RocketChatSettings } from '@rocket.chat/sdk'; 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" -export const headers = { +export const headers: CustomHeaders = { 'User-Agent': `RC Mobile; ${ Platform.OS } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` }; let _basicAuth; -export const setBasicAuth = basicAuth => { +export const setBasicAuth = (basicAuth: string): void => { _basicAuth = basicAuth; if (basicAuth) { RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` }; @@ -24,12 +29,15 @@ export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; RocketChatSettings.customHeaders = headers; -export default (url, options = {}) => { +export default (url: string, options: { headers?: Headers; signal?: AbortSignal } = {}): Promise => { let customOptions = { ...options, headers: RocketChatSettings.customHeaders }; if (options && options.headers) { customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } }; } + // TODO: Refactor when migrate rocketchat.js + // @ts-ignore if (RocketChat.controller) { + // @ts-ignore const { signal } = RocketChat.controller; customOptions = { ...customOptions, signal }; } diff --git a/app/utils/fileUpload/index.ios.js b/app/utils/fileUpload/index.ios.ts similarity index 65% rename from app/utils/fileUpload/index.ios.js rename to app/utils/fileUpload/index.ios.ts index a97640553..ae5cfabc2 100644 --- a/app/utils/fileUpload/index.ios.js +++ b/app/utils/fileUpload/index.ios.ts @@ -1,19 +1,25 @@ +import { IFileUpload } from './interfaces'; + class Upload { + public xhr: XMLHttpRequest; + + public formData: FormData; + constructor() { this.xhr = new XMLHttpRequest(); this.formData = new FormData(); } - then = callback => { + then = (callback: (param: { respInfo: XMLHttpRequest }) => XMLHttpRequest) => { this.xhr.onload = () => callback({ respInfo: this.xhr }); this.xhr.send(this.formData); }; - catch = callback => { + catch = (callback: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null) => { this.xhr.onerror = callback; }; - uploadProgress = callback => { + uploadProgress = (callback: (param: number, arg1: number) => any) => { this.xhr.upload.onprogress = ({ total, loaded }) => callback(loaded, total); }; @@ -24,7 +30,7 @@ class Upload { } class FileUpload { - fetch = (method, url, headers, data) => { + fetch = (method: string, url: string, headers: { [x: string]: string }, data: IFileUpload[]) => { const upload = new Upload(); upload.xhr.open(method, url); @@ -35,6 +41,7 @@ class FileUpload { data.forEach(item => { if (item.uri) { upload.formData.append(item.name, { + // @ts-ignore uri: item.uri, type: item.type, name: item.filename diff --git a/app/utils/fileUpload/index.android.js b/app/utils/fileUpload/index.ts similarity index 64% rename from app/utils/fileUpload/index.android.js rename to app/utils/fileUpload/index.ts index 5c45c27bd..1d2bdb312 100644 --- a/app/utils/fileUpload/index.android.js +++ b/app/utils/fileUpload/index.ts @@ -1,7 +1,11 @@ import RNFetchBlob from 'rn-fetch-blob'; +import { IFileUpload } from './interfaces'; + +type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put'; + class FileUpload { - fetch = (method, url, headers, data) => { + fetch = (method: TMethods, url: string, headers: { [key: string]: string }, data: IFileUpload[]) => { const formData = data.map(item => { if (item.uri) { return { diff --git a/app/utils/fileUpload/interfaces.ts b/app/utils/fileUpload/interfaces.ts new file mode 100644 index 000000000..a3002f727 --- /dev/null +++ b/app/utils/fileUpload/interfaces.ts @@ -0,0 +1,7 @@ +export interface IFileUpload { + name: string; + uri?: string; + type: string; + filename: string; + data: any; +} diff --git a/app/utils/goRoom.js b/app/utils/goRoom.ts similarity index 58% rename from app/utils/goRoom.js rename to app/utils/goRoom.ts index 1025a17d4..dc8a31882 100644 --- a/app/utils/goRoom.js +++ b/app/utils/goRoom.ts @@ -1,7 +1,17 @@ +import { ChatsStackParamList } from '../stacks/types'; import Navigation from '../lib/Navigation'; 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; if (isMasterDetail) { @@ -20,7 +30,22 @@ const navigate = ({ item, isMasterDetail, ...props }) => { }); }; -export const goRoom = async ({ item = {}, isMasterDetail = false, ...props }) => { +interface IItem extends Partial { + rid: string; + name: string; + t: SubscriptionType; +} + +export const goRoom = async ({ + item, + isMasterDetail = false, + ...props +}: { + item: IItem; + isMasterDetail: boolean; + navigationMethod?: any; + jumpToMessageId?: string; +}): Promise => { if (item.t === 'd' && item.search) { // if user is using the search we need first to join/create room try { @@ -30,8 +55,8 @@ export const goRoom = async ({ item = {}, isMasterDetail = false, ...props }) => return navigate({ item: { rid: result.room._id, - name: username, - t: 'd' + name: username!, + t: SubscriptionType.DIRECT }, isMasterDetail, ...props diff --git a/app/utils/info.js b/app/utils/info.js deleted file mode 100644 index 5d72f200e..000000000 --- a/app/utils/info.js +++ /dev/null @@ -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 } - ); diff --git a/app/utils/info.ts b/app/utils/info.ts new file mode 100644 index 000000000..da882ee41 --- /dev/null +++ b/app/utils/info.ts @@ -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 } + ); diff --git a/app/utils/isReadOnly.js b/app/utils/isReadOnly.ts similarity index 59% rename from app/utils/isReadOnly.js rename to app/utils/isReadOnly.ts index 62ae4fffe..d94b73c49 100644 --- a/app/utils/isReadOnly.js +++ b/app/utils/isReadOnly.ts @@ -1,16 +1,21 @@ import RocketChat from '../lib/rocketchat'; 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 const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly']; const permission = await RocketChat.hasPermission([postReadOnlyPermission], rid); 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 => { if (room.archived) { return true; } diff --git a/app/utils/isValidEmail.js b/app/utils/isValidEmail.ts similarity index 78% rename from app/utils/isValidEmail.js rename to app/utils/isValidEmail.ts index a8bd490f9..e230fc328 100644 --- a/app/utils/isValidEmail.js +++ b/app/utils/isValidEmail.ts @@ -1,4 +1,4 @@ -export default function isValidEmail(email) { +export default function isValidEmail(email: string): boolean { /* eslint-disable no-useless-escape */ 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,}))$/; diff --git a/app/utils/layoutAnimation.js b/app/utils/layoutAnimation.ts similarity index 100% rename from app/utils/layoutAnimation.js rename to app/utils/layoutAnimation.ts diff --git a/app/utils/localAuthentication.js b/app/utils/localAuthentication.ts similarity index 73% rename from app/utils/localAuthentication.js rename to app/utils/localAuthentication.ts index 29f256850..f43599633 100644 --- a/app/utils/localAuthentication.js +++ b/app/utils/localAuthentication.ts @@ -16,16 +16,17 @@ import { } from '../constants/localAuthentication'; import I18n from '../i18n'; import { setLocalAuthenticated } from '../actions/login'; +import { TServerModel } from '../definitions/IServer'; import EventEmitter from './events'; import { isIOS } from './deviceInfo'; -export const saveLastLocalAuthenticationSession = async (server, serverRecord) => { +export const saveLastLocalAuthenticationSession = async (server: string, serverRecord?: TServerModel): Promise => { const serversDB = database.servers; const serversCollection = serversDB.get('servers'); - await serversDB.action(async () => { + await serversDB.write(async () => { try { if (!serverRecord) { - serverRecord = await serversCollection.find(server); + serverRecord = (await serversCollection.find(server)) as TServerModel; } await serverRecord.update(record => { 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 => AsyncStorage.multiRemove([LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY]); -const openModal = hasBiometry => - new Promise(resolve => { +const openModal = (hasBiometry: boolean) => + new Promise(resolve => { EventEmitter.emit(LOCAL_AUTHENTICATE_EMITTER, { submit: () => resolve(), hasBiometry }); }); -const openChangePasscodeModal = ({ force }) => - new Promise((resolve, reject) => { +const openChangePasscodeModal = ({ force }: { force: boolean }) => + new Promise((resolve, reject) => { EventEmitter.emit(CHANGE_PASSCODE_EMITTER, { - submit: passcode => resolve(passcode), + submit: (passcode: string) => resolve(passcode), cancel: () => reject(), force }); }); -export const changePasscode = async ({ force = false }) => { +export const changePasscode = async ({ force = false }: { force: boolean }): Promise => { const passcode = await openChangePasscodeModal({ force }); await UserPreferences.setStringAsync(PASSCODE_KEY, sha256(passcode)); }; -export const biometryAuth = force => +export const biometryAuth = (force?: boolean): Promise => LocalAuthentication.authenticateAsync({ disableDeviceFallback: true, 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 * 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 result = await biometryAuth(true); - await serversDB.action(async () => { + await serversDB.write(async () => { try { await serverRecord.update(record => { 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); if (!storedPasscode) { await changePasscode({ force }); @@ -96,13 +103,13 @@ export const checkHasPasscode = async ({ force = true, serverRecord }) => { return Promise.resolve(); }; -export const localAuthenticate = async server => { +export const localAuthenticate = async (server: string): Promise => { const serversDB = database.servers; const serversCollection = serversDB.get('servers'); - let serverRecord; + let serverRecord: TServerModel; try { - serverRecord = await serversCollection.find(server); + serverRecord = (await serversCollection.find(server)) as TServerModel; } catch (error) { return Promise.reject(); } @@ -125,7 +132,7 @@ export const localAuthenticate = async server => { const diffToLastSession = moment().diff(serverRecord?.lastLocalAuthenticatedSession, 'seconds'); // 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 store.dispatch(setLocalAuthenticated(false)); @@ -150,7 +157,7 @@ export const localAuthenticate = async server => { } }; -export const supportedBiometryLabel = async () => { +export const supportedBiometryLabel = async (): Promise => { try { const enrolled = await LocalAuthentication.isEnrolledAsync(); diff --git a/app/utils/log/events.js b/app/utils/log/events.ts similarity index 100% rename from app/utils/log/events.js rename to app/utils/log/events.ts diff --git a/app/utils/log/index.js b/app/utils/log/index.ts similarity index 66% rename from app/utils/log/index.js rename to app/utils/log/index.ts index 41074832f..d52bd0e94 100644 --- a/app/utils/log/index.js +++ b/app/utils/log/index.ts @@ -4,13 +4,13 @@ import { isFDroidBuild } from '../../constants/environment'; import events from './events'; const analytics = firebaseAnalytics || ''; -let bugsnag = ''; -let crashlytics; +let bugsnag: any = ''; +let crashlytics: any; let reportCrashErrors = true; let reportAnalyticsEvents = true; -export const getReportCrashErrorsValue = () => reportCrashErrors; -export const getReportAnalyticsEventsValue = () => reportAnalyticsEvents; +export const getReportCrashErrorsValue = (): boolean => reportCrashErrors; +export const getReportAnalyticsEventsValue = (): boolean => reportAnalyticsEvents; if (!isFDroidBuild) { bugsnag = require('@bugsnag/react-native').default; @@ -18,7 +18,7 @@ if (!isFDroidBuild) { onBreadcrumb() { return reportAnalyticsEvents; }, - onError(error) { + onError(error: { breadcrumbs: string[] }) { if (!reportAnalyticsEvents) { error.breadcrumbs = []; } @@ -34,13 +34,13 @@ export { events }; let metadata = {}; -export const logServerVersion = serverVersion => { +export const logServerVersion = (serverVersion: string): void => { metadata = { serverVersion }; }; -export const logEvent = (eventName, payload) => { +export const logEvent = (eventName: string, payload?: { [key: string]: any }): void => { try { if (!isFDroidBuild) { 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) { analytics().setCurrentScreen(currentScreen); bugsnag.leaveBreadcrumb(currentScreen, { type: 'navigation' }); } }; -export const toggleCrashErrorsReport = value => { +export const toggleCrashErrorsReport = (value: boolean): boolean => { crashlytics().setCrashlyticsCollectionEnabled(value); return (reportCrashErrors = value); }; -export const toggleAnalyticsEventsReport = value => { +export const toggleAnalyticsEventsReport = (value: boolean): boolean => { analytics().setAnalyticsCollectionEnabled(value); return (reportAnalyticsEvents = value); }; -export default e => { +export default (e: any): void => { 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 }); }); if (!isFDroidBuild) { diff --git a/app/utils/media.js b/app/utils/media.ts similarity index 65% rename from app/utils/media.js rename to app/utils/media.ts index 07f6f58d7..78b1c29f3 100644 --- a/app/utils/media.js +++ b/app/utils/media.ts @@ -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)) { return { success: true }; } @@ -13,11 +20,11 @@ export const canUploadFile = (file, allowList, maxFileSize, permissionToUploadFi return { success: true }; } const allowedMime = allowList.split(','); - if (allowedMime.includes(file.mime)) { + if (allowedMime.includes(file.mime!)) { return { success: true }; } 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))) { return { success: true }; } diff --git a/app/utils/messageTypes.js b/app/utils/messageTypes.ts similarity index 100% rename from app/utils/messageTypes.js rename to app/utils/messageTypes.ts diff --git a/app/utils/moment.js b/app/utils/moment.ts similarity index 57% rename from app/utils/moment.js rename to app/utils/moment.ts index 064b0f7f9..3379429c7 100644 --- a/app/utils/moment.js +++ b/app/utils/moment.ts @@ -1,4 +1,4 @@ -const localeKeys = { +const localeKeys: { [key: string]: string } = { en: 'en', ru: 'ru', 'pt-BR': 'pt-br', @@ -13,4 +13,4 @@ const localeKeys = { 'zh-TW': 'zh-tw' }; -export const toMomentLocale = locale => localeKeys[locale]; +export const toMomentLocale = (locale: string): string => localeKeys[locale]; diff --git a/app/utils/navigation/animations.js b/app/utils/navigation/animations.ts similarity index 71% rename from app/utils/navigation/animations.js rename to app/utils/navigation/animations.ts index 9f99764c4..a9f184088 100644 --- a/app/utils/navigation/animations.js +++ b/app/utils/navigation/animations.ts @@ -1,12 +1,14 @@ 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 conditional from './conditional'; const { multiply } = Animated; -const forFadeFromCenter = ({ current, closing }) => { +const forFadeFromCenter: StackCardStyleInterpolator = ({ current, closing }) => { const opacity = conditional( closing, current.progress, @@ -23,7 +25,7 @@ const forFadeFromCenter = ({ current, closing }) => { }; }; -const FadeIn = { +const FadeIn: TransitionSpec = { animation: 'timing', config: { duration: 250, @@ -31,7 +33,7 @@ const FadeIn = { } }; -const FadeOut = { +const FadeOut: TransitionSpec = { animation: 'timing', config: { duration: 150, @@ -48,7 +50,7 @@ export const FadeFromCenterModal = { cardStyleInterpolator: forFadeFromCenter }; -const forStackAndroid = ({ current, inverted, layouts: { screen } }) => { +const forStackAndroid: StackCardStyleInterpolator = ({ current, inverted, layouts: { screen } }) => { const translateX = multiply( current.progress.interpolate({ inputRange: [0, 1], @@ -70,7 +72,7 @@ const forStackAndroid = ({ current, inverted, layouts: { screen } }) => { }; }; -const StackAndroid = { +const StackAndroid: TransitionPreset = { gestureDirection: 'horizontal', transitionSpec: { open: FadeIn, diff --git a/app/utils/navigation/conditional.js b/app/utils/navigation/conditional.ts similarity index 87% rename from app/utils/navigation/conditional.js rename to app/utils/navigation/conditional.ts index 015c52ae1..84c76d83a 100644 --- a/app/utils/navigation/conditional.js +++ b/app/utils/navigation/conditional.ts @@ -10,7 +10,11 @@ const { add, multiply } = Animated; * @param main Animated Node to use if the condition is `true` * @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. // 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. diff --git a/app/utils/openLink.js b/app/utils/openLink.ts similarity index 83% rename from app/utils/openLink.js rename to app/utils/openLink.ts index 92df16a70..4048b3add 100644 --- a/app/utils/openLink.js +++ b/app/utils/openLink.ts @@ -14,7 +14,7 @@ const scheme = { brave: 'brave:' }; -const appSchemeURL = (url, browser) => { +const appSchemeURL = (url: string, browser: string): string => { let schemeUrl = url; const parsedUrl = parse(url, true); const { protocol } = parsedUrl; @@ -35,7 +35,7 @@ const appSchemeURL = (url, browser) => { return schemeUrl; }; -const openLink = async (url, theme = 'light') => { +const openLink = async (url: string, theme = 'light'): Promise => { try { const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY); @@ -43,11 +43,12 @@ const openLink = async (url, theme = 'light') => { await WebBrowser.openBrowserAsync(url, { toolbarColor: themes[theme].headerBackground, controlsColor: themes[theme].headerTintColor, - collapseToolbar: true, + // https://github.com/expo/expo/pull/4923 + enableBarCollapsing: true, showTitle: true }); } else { - const schemeUrl = appSchemeURL(url, browser.replace(':', '')); + const schemeUrl = appSchemeURL(url, browser!.replace(':', '')); await Linking.openURL(schemeUrl); } } catch { diff --git a/app/utils/random.js b/app/utils/random.ts similarity index 80% rename from app/utils/random.js rename to app/utils/random.ts index 8f6adb880..2d2cd178b 100644 --- a/app/utils/random.js +++ b/app/utils/random.ts @@ -1,4 +1,4 @@ -export default function random(length) { +export default function random(length: number): string { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < length; i += 1) { diff --git a/app/utils/review.js b/app/utils/review.ts similarity index 96% rename from app/utils/review.js rename to app/utils/review.ts index 15f1cf968..bbbb498be 100644 --- a/app/utils/review.js +++ b/app/utils/review.ts @@ -15,7 +15,7 @@ const reviewDelay = 2000; const numberOfDays = 7; const numberOfPositiveEvent = 5; -const daysBetween = (date1, date2) => { +const daysBetween = (date1: Date, date2: Date): number => { const one_day = 1000 * 60 * 60 * 24; const date1_ms = date1.getTime(); const date2_ms = date2.getTime(); @@ -32,7 +32,7 @@ const onCancelPress = () => { } }; -export const onReviewPress = async () => { +export const onReviewPress = async (): Promise => { logEvent(events.SE_REVIEW_THIS_APP); await onCancelPress(); try { diff --git a/app/utils/room.js b/app/utils/room.js deleted file mode 100644 index 67df65a98..000000000 --- a/app/utils/room.js +++ /dev/null @@ -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; diff --git a/app/utils/room.ts b/app/utils/room.ts new file mode 100644 index 000000000..3e4e0ef44 --- /dev/null +++ b/app/utils/room.ts @@ -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; diff --git a/app/utils/server.js b/app/utils/server.ts similarity index 84% rename from app/utils/server.js rename to app/utils/server.ts index e7be96b30..52064757b 100644 --- a/app/utils/server.js +++ b/app/utils/server.ts @@ -3,7 +3,7 @@ url = 'https://open.rocket.chat/method' hostname = 'open.rocket.chat' */ -export const extractHostname = url => { +export const extractHostname = (url: string): string => { let hostname; if (url.indexOf('//') > -1) { diff --git a/app/utils/shortnameToUnicode/ascii.js b/app/utils/shortnameToUnicode/ascii.ts similarity index 98% rename from app/utils/shortnameToUnicode/ascii.js rename to app/utils/shortnameToUnicode/ascii.ts index 4d9d04cd3..7eca5f7df 100644 --- a/app/utils/shortnameToUnicode/ascii.js +++ b/app/utils/shortnameToUnicode/ascii.ts @@ -3,7 +3,7 @@ /* eslint-disable object-curly-spacing */ /* eslint-disable comma-spacing */ /* eslint-disable key-spacing */ -const ascii = { +const ascii: { [key: string]: string } = { '*\\0/*': '🙆', '*\\O/*': '🙆', '-___-': '😑', diff --git a/app/utils/shortnameToUnicode/emojis.js b/app/utils/shortnameToUnicode/emojis.ts similarity index 99% rename from app/utils/shortnameToUnicode/emojis.js rename to app/utils/shortnameToUnicode/emojis.ts index 14cd61337..6a8a63c3a 100644 --- a/app/utils/shortnameToUnicode/emojis.js +++ b/app/utils/shortnameToUnicode/emojis.ts @@ -3,7 +3,7 @@ /* eslint-disable object-curly-spacing */ /* eslint-disable comma-spacing */ /* eslint-disable key-spacing */ -const emojis = { +const emojis: { [key: string]: string } = { ':england:': '🏴󠁧󠁢󠁥󠁮󠁧󠁿', ':scotland:': '🏴󠁧󠁢󠁳󠁣󠁴󠁿', ':wales:': '🏴󠁧󠁢󠁷󠁬󠁳󠁿', diff --git a/app/utils/shortnameToUnicode/index.js b/app/utils/shortnameToUnicode/index.ts similarity index 80% rename from app/utils/shortnameToUnicode/index.js rename to app/utils/shortnameToUnicode/index.ts index 0a54aa3a3..b533da8ff 100644 --- a/app/utils/shortnameToUnicode/index.js +++ b/app/utils/shortnameToUnicode/index.ts @@ -2,11 +2,11 @@ import emojis from './emojis'; import ascii, { asciiRegexp } from './ascii'; 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 unescapeHTML = string => { - const unescaped = { +const unescapeHTML = (string: string) => { + 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]); }; -const shortnameToUnicode = str => { +const shortnameToUnicode = (str: string): string => { str = str.replace(shortnamePattern, replaceShortNameWithUnicode); str = str.replace(regAscii, (entire, m1, m2, m3) => { diff --git a/app/utils/sslPinning.js b/app/utils/sslPinning.ts similarity index 75% rename from app/utils/sslPinning.js rename to app/utils/sslPinning.ts index c3e2128c9..42245c98a 100644 --- a/app/utils/sslPinning.js +++ b/app/utils/sslPinning.ts @@ -9,13 +9,18 @@ import { extractHostname } from './server'; const { SSLPinning } = NativeModules; 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 certificate = { + const certificate: ICertificate = { path: extractFileScheme(certificatePath), password }; @@ -29,6 +34,7 @@ const RCSSLPinning = Platform.select({ new Promise(async (resolve, reject) => { try { const res = await DocumentPicker.pick({ + // @ts-ignore type: ['com.rsa.pkcs-12'] }); const { uri, name } = res; @@ -42,7 +48,7 @@ const RCSSLPinning = Platform.select({ try { const certificatePath = getPath(name); await FileSystem.copyAsync({ from: uri, to: certificatePath }); - await persistCertificate(name, password); + await persistCertificate(name, password!); resolve(name); } catch (e) { reject(e); @@ -56,10 +62,10 @@ const RCSSLPinning = Platform.select({ reject(e); } }), - setCertificate: async (name, server) => { + setCertificate: async (name: string, server: string) => { if (name) { - let certificate = await UserPreferences.getMapAsync(name); - if (!certificate.path.match(extractFileScheme(documentDirectory))) { + let certificate = (await UserPreferences.getMapAsync(name)) as ICertificate; + if (!certificate.path.match(extractFileScheme(documentDirectory!))) { certificate = await persistCertificate(name, certificate.password); } await UserPreferences.setMapAsync(extractHostname(server), certificate); diff --git a/app/utils/theme.js b/app/utils/theme.ts similarity index 71% rename from app/utils/theme.js rename to app/utils/theme.ts index c9038941d..0e9d8e058 100644 --- a/app/utils/theme.js +++ b/app/utils/theme.ts @@ -2,12 +2,13 @@ import { Appearance } from 'react-native-appearance'; import changeNavigationBarColor from 'react-native-navigation-bar-color'; import setRootViewColor from 'rn-root-view'; +import { IThemePreference, TThemeMode } from '../definitions/ITheme'; import { themes } from '../constants/colors'; import { isAndroid } from './deviceInfo'; -let themeListener; +let themeListener: { remove: () => void } | null; -export const defaultTheme = () => { +export const defaultTheme = (): TThemeMode => { const systemTheme = Appearance.getColorScheme(); if (systemTheme && systemTheme !== 'no-preference') { return systemTheme; @@ -15,7 +16,7 @@ export const defaultTheme = () => { return 'light'; }; -export const getTheme = themePreferences => { +export const getTheme = (themePreferences: IThemePreference): string => { const { darkLevel, currentTheme } = themePreferences; let theme = currentTheme; if (currentTheme === 'automatic') { @@ -24,7 +25,7 @@ export const getTheme = themePreferences => { return theme === 'dark' ? darkLevel : 'light'; }; -export const newThemeState = (prevState, newTheme) => { +export const newThemeState = (prevState: { themePreferences: IThemePreference }, newTheme: IThemePreference) => { // new theme preferences const themePreferences = { ...prevState.themePreferences, @@ -35,12 +36,13 @@ export const newThemeState = (prevState, newTheme) => { return { themePreferences, theme: getTheme(themePreferences) }; }; -export const setNativeTheme = async themePreferences => { +export const setNativeTheme = async (themePreferences: IThemePreference): Promise => { const theme = getTheme(themePreferences); if (isAndroid) { const iconsLight = theme === 'light'; 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) { // 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; if (!themeListener && currentTheme === 'automatic') { // not use listener params because we use getTheme diff --git a/app/utils/throttle.js b/app/utils/throttle.js deleted file mode 100644 index 88751335f..000000000 --- a/app/utils/throttle.js +++ /dev/null @@ -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; -} diff --git a/app/utils/touch.js b/app/utils/touch.tsx similarity index 55% rename from app/utils/touch.js rename to app/utils/touch.tsx index 0bfece04a..3573c87c5 100644 --- a/app/utils/touch.js +++ b/app/utils/touch.tsx @@ -1,19 +1,27 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { RectButton } from 'react-native-gesture-handler'; +import { RectButton, RectButtonProps } from 'react-native-gesture-handler'; import { themes } from '../constants/colors'; -class Touch extends React.Component { - setNativeProps(props) { +interface ITouchProps extends RectButtonProps { + children: React.ReactNode; + theme: string; + accessibilityLabel?: string; + testID?: string; +} + +class Touch extends React.Component { + private ref: any; + + setNativeProps(props: ITouchProps): void { this.ref.setNativeProps(props); } - getRef = ref => { + getRef = (ref: RectButton): void => { this.ref = ref; }; - render() { + render(): JSX.Element { const { children, onPress, theme, underlayColor, ...props } = this.props; 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; diff --git a/app/utils/twoFactor.js b/app/utils/twoFactor.ts similarity index 68% rename from app/utils/twoFactor.js rename to app/utils/twoFactor.ts index 6f2fa9c9d..a52ff93fd 100644 --- a/app/utils/twoFactor.js +++ b/app/utils/twoFactor.ts @@ -3,13 +3,18 @@ import { settings } from '@rocket.chat/sdk'; import { TWO_FACTOR } from '../containers/TwoFactor'; 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) => { EventEmitter.emit(TWO_FACTOR, { method, invalid, cancel: () => reject(), - submit: code => { + submit: (code: string) => { settings.customHeaders = { ...settings.customHeaders, 'x-2fa-code': code, diff --git a/app/utils/url.js b/app/utils/url.ts similarity index 77% rename from app/utils/url.js rename to app/utils/url.ts index 623524d7a..501795973 100644 --- a/app/utils/url.js +++ b/app/utils/url.ts @@ -1,4 +1,4 @@ -export const isValidURL = url => { +export const isValidURL = (url: string): boolean => { const pattern = new RegExp( '^(https?:\\/\\/)?' + // protocol '((([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:// -export const useSsl = url => !/http:\/\//.test(url); +export const useSsl = (url: string): boolean => !/http:\/\//.test(url); diff --git a/app/views/CreateDiscussionView/SelectChannel.tsx b/app/views/CreateDiscussionView/SelectChannel.tsx index e7653973f..41f7ab0b1 100644 --- a/app/views/CreateDiscussionView/SelectChannel.tsx +++ b/app/views/CreateDiscussionView/SelectChannel.tsx @@ -32,8 +32,6 @@ const SelectChannel = ({ }, 300); const getAvatar = (item: any) => - // TODO: remove this ts-ignore when migrate the file: app/utils/avatar.js - // @ts-ignore avatarURL({ text: RocketChat.getRoomAvatar(item), type: item.t, diff --git a/app/views/CreateDiscussionView/SelectUsers.tsx b/app/views/CreateDiscussionView/SelectUsers.tsx index 65a4e0a4a..d63c5ae6a 100644 --- a/app/views/CreateDiscussionView/SelectUsers.tsx +++ b/app/views/CreateDiscussionView/SelectUsers.tsx @@ -12,6 +12,7 @@ import { MultiSelect } from '../../containers/UIKit/MultiSelect'; import { themes } from '../../constants/colors'; import styles from './styles'; import { ICreateDiscussionViewSelectUsers } from './interfaces'; +import { SubscriptionType } from '../../definitions/ISubscription'; interface IUser { name: string; @@ -62,11 +63,9 @@ const SelectUsers = ({ }, 300); const getAvatar = (item: any) => - // TODO: remove this ts-ignore when migrate the file: app/utils/avatar.js - // @ts-ignore avatarURL({ text: RocketChat.getRoomAvatar(item), - type: 'd', + type: SubscriptionType.DIRECT, user: { id: userId, token }, server, avatarETag: item.avatarETag, diff --git a/app/views/CreateDiscussionView/interfaces.ts b/app/views/CreateDiscussionView/interfaces.ts index e9d076b16..6009881c1 100644 --- a/app/views/CreateDiscussionView/interfaces.ts +++ b/app/views/CreateDiscussionView/interfaces.ts @@ -2,6 +2,7 @@ import { RouteProp } from '@react-navigation/core'; import { StackNavigationProp } from '@react-navigation/stack'; import { NewMessageStackParamList } from '../../stacks/types'; +import { SubscriptionType } from '../../definitions/ISubscription'; export interface ICreateChannelViewProps { navigation: StackNavigationProp; @@ -15,7 +16,7 @@ export interface ICreateChannelViewProps { loading: boolean; result: { rid: string; - t: string; + t: SubscriptionType; prid: string; }; failure: boolean; diff --git a/app/views/E2EEncryptionSecurityView.tsx b/app/views/E2EEncryptionSecurityView.tsx index d5b0b27a2..347590430 100644 --- a/app/views/E2EEncryptionSecurityView.tsx +++ b/app/views/E2EEncryptionSecurityView.tsx @@ -75,8 +75,6 @@ class E2EEncryptionSecurityView extends React.Component { - // TODO: Remove ts-ignore when migrate the showConfirmationAlert - // @ts-ignore showConfirmationAlert({ title: I18n.t('Are_you_sure_question_mark'), message: I18n.t('E2E_encryption_reset_message'), diff --git a/app/views/NewServerView/index.tsx b/app/views/NewServerView/index.tsx index 53594d9c0..93e493bea 100644 --- a/app/views/NewServerView/index.tsx +++ b/app/views/NewServerView/index.tsx @@ -269,11 +269,10 @@ class NewServerView extends React.Component { uriToPath = (uri: string) => uri.replace('file://', ''); handleRemove = () => { - // TODO: Remove ts-ignore when migrate the showConfirmationAlert - // @ts-ignore showConfirmationAlert({ message: I18n.t('You_will_unset_a_certificate_for_this_server'), confirmationText: I18n.t('Remove'), + // @ts-ignore onPress: this.setState({ certificate: null }) // We not need delete file from DocumentPicker because it is a temp file }); }; diff --git a/app/views/ProfileView/index.tsx b/app/views/ProfileView/index.tsx index d0ca64eec..fc409002d 100644 --- a/app/views/ProfileView/index.tsx +++ b/app/views/ProfileView/index.tsx @@ -424,7 +424,6 @@ class ProfileView extends React.Component logoutOtherLocations = () => { logEvent(events.PL_OTHER_LOCATIONS); - // @ts-ignore showConfirmationAlert({ message: I18n.t('You_will_be_logged_out_from_other_locations'), confirmationText: I18n.t('Logout'), diff --git a/app/views/ProfileView/interfaces.ts b/app/views/ProfileView/interfaces.ts index bfec50c0d..0fe592f32 100644 --- a/app/views/ProfileView/interfaces.ts +++ b/app/views/ProfileView/interfaces.ts @@ -26,9 +26,9 @@ export interface IParams { } export interface IAvatarButton { - key: React.Key; + key: string; child: React.ReactNode; - onPress: Function; + onPress: () => void; disabled: boolean; } diff --git a/app/views/ScreenLockConfigView.tsx b/app/views/ScreenLockConfigView.tsx index 57638f417..d6307f5e9 100644 --- a/app/views/ScreenLockConfigView.tsx +++ b/app/views/ScreenLockConfigView.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { Switch } from 'react-native'; import { connect } from 'react-redux'; import { StackNavigationOptions } from '@react-navigation/stack'; -import Model from '@nozbe/watermelondb/Model'; import { Subscription } from 'rxjs'; import I18n from '../i18n'; @@ -15,15 +14,10 @@ import { changePasscode, checkHasPasscode, supportedBiometryLabel } from '../uti import { DEFAULT_AUTO_LOCK } from '../constants/localAuthentication'; import SafeAreaView from '../containers/SafeAreaView'; import { events, logEvent } from '../utils/log'; +import { TServerModel } from '../definitions/IServer'; const DEFAULT_BIOMETRY = false; -interface IServerRecords extends Model { - autoLock?: boolean; - autoLockTime?: number; - biometry?: boolean; -} - interface IItem { title: string; value: number; @@ -38,14 +32,14 @@ interface IScreenLockConfigViewProps { } interface IScreenLockConfigViewState { - autoLock?: boolean; + autoLock: boolean; autoLockTime?: number | null; biometry?: boolean; - biometryLabel: null; + biometryLabel: string | null; } class ScreenLockConfigView extends React.Component { - private serverRecord?: IServerRecords; + private serverRecord?: TServerModel; private observable?: Subscription; @@ -98,7 +92,7 @@ class ScreenLockConfigView extends React.Component { handleLogout = () => { logEvent(events.SE_LOG_OUT); - // @ts-ignore showConfirmationAlert({ message: I18n.t('You_will_be_logged_out_of_this_application'), confirmationText: I18n.t('Logout'), @@ -98,7 +97,6 @@ class SettingsView extends React.Component { handleClearCache = () => { logEvent(events.SE_CLEAR_LOCAL_SERVER_CACHE); - /* @ts-ignore */ showConfirmationAlert({ message: I18n.t('This_will_clear_all_your_offline_data'), confirmationText: I18n.t('Clear'), diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index 1c9f0e415..e4294c600 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -25,12 +25,12 @@ import { sanitizeLikeString } from '../../lib/database/utils'; import styles from './styles'; import ShareListHeader from './Header'; -interface IFile { +interface IDataFromShare { value: string; type: string; } -interface IAttachment { +interface IFileToShare { filename: string; description: string; size: number; @@ -61,7 +61,7 @@ interface IState { searchResults: IChat[]; chats: IChat[]; serversCount: number; - attachments: IAttachment[]; + attachments: IFileToShare[]; text: string; loading: boolean; serverInfo: IServerInfo; @@ -121,7 +121,7 @@ class ShareListView extends React.Component { async componentDidMount() { const { server } = this.props; try { - const data = (await ShareExtension.data()) as IFile[]; + const data = (await ShareExtension.data()) as IDataFromShare[]; if (isAndroid) { await this.askForPermission(data); } @@ -136,7 +136,7 @@ class ShareListView extends React.Component { size: file.size, mime: mime.lookup(file.uri), path: file.uri - })) as IAttachment[]; + })) as IFileToShare[]; const text = data.filter(item => item.type === 'text').reduce((acc, item) => `${item.value}\n${acc}`, ''); this.setState({ text, @@ -294,7 +294,7 @@ class ShareListView extends React.Component { } }; - askForPermission = async (data: IFile[]) => { + askForPermission = async (data: IDataFromShare[]) => { const mediaIndex = data.findIndex(item => item.type === 'media'); if (mediaIndex !== -1) { const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, permission); diff --git a/app/views/ShareView/index.tsx b/app/views/ShareView/index.tsx index d77ba022b..e91b8a326 100644 --- a/app/views/ShareView/index.tsx +++ b/app/views/ShareView/index.tsx @@ -39,7 +39,7 @@ interface IShareViewState { room: ISubscription; thread: any; // change maxFileSize: number; - mediaAllowList: number; + mediaAllowList: string; } interface IShareViewProps { @@ -52,7 +52,7 @@ interface IShareViewProps { token: string; }; server: string; - FileUpload_MediaTypeWhiteList?: number; + FileUpload_MediaTypeWhiteList?: string; FileUpload_MaxFileSize?: number; } diff --git a/app/views/ThemeView.tsx b/app/views/ThemeView.tsx index 8ab9010c4..636dec487 100644 --- a/app/views/ThemeView.tsx +++ b/app/views/ThemeView.tsx @@ -11,17 +11,18 @@ import { supportSystemTheme } from '../utils/deviceInfo'; import SafeAreaView from '../containers/SafeAreaView'; import UserPreferences from '../lib/userPreferences'; import { events, logEvent } from '../utils/log'; +import { IThemePreference, TThemeMode, TDarkLevel } from '../definitions/ITheme'; const THEME_GROUP = 'THEME_GROUP'; const DARK_GROUP = 'DARK_GROUP'; -const SYSTEM_THEME = { +const SYSTEM_THEME: ITheme = { label: 'Automatic', value: 'automatic', group: THEME_GROUP }; -const THEMES = [ +const THEMES: ITheme[] = [ { label: 'Light', value: 'light', @@ -53,15 +54,10 @@ const darkGroup = THEMES.filter(item => item.group === DARK_GROUP); interface ITheme { label: string; - value: string; + value: TThemeMode | TDarkLevel; group: string; } -interface IThemePreference { - currentTheme?: string; - darkLevel?: string; -} - interface IThemeViewProps { theme: string; themePreferences: IThemePreference; @@ -89,19 +85,19 @@ class ThemeView extends React.Component { const { themePreferences } = this.props; const { darkLevel, currentTheme } = themePreferences; const { value, group } = item; - let changes: IThemePreference = {}; + let changes: Partial = {}; if (group === THEME_GROUP && currentTheme !== value) { logEvent(events.THEME_SET_THEME_GROUP, { theme_group: value }); - changes = { currentTheme: value }; + changes = { currentTheme: value as TThemeMode }; } if (group === DARK_GROUP && darkLevel !== value) { logEvent(events.THEME_SET_DARK_LEVEL, { dark_level: value }); - changes = { darkLevel: value }; + changes = { darkLevel: value as TDarkLevel }; } this.setTheme(changes); }; - setTheme = async (theme: IThemePreference) => { + setTheme = async (theme: Partial) => { const { setTheme, themePreferences } = this.props; const newTheme = { ...themePreferences, ...theme }; setTheme(newTheme);