Compare commits

...

71 Commits

Author SHA1 Message Date
AlexAlexandre 7b9f00510d [improve] - fix the errors with auto-save 2021-08-19 21:01:17 -03:00
AlexAlexandre 3604e7d4b3 [improve] - adding the last rules for ts and js 2021-08-19 20:55:32 -03:00
AlexAlexandre 69888c4944 [improve] - add prettier 2021-08-18 18:25:34 -03:00
AlexAlexandre 0738bfb620 [improve] - setting up some rules and integration with typescript 2021-08-18 18:24:23 -03:00
AlexAlexandre 7cf55c1f3b Merge branch 'improvement.typescript-migration' into improvement.eslint-change-to-ts 2021-08-18 10:59:27 -03:00
AlexAlexandre 1737756b43 Merge branch 'develop' into improvement.typescript-migration
# Conflicts:
#	app/containers/Loading.tsx
2021-08-16 20:57:40 -03:00
AlexAlexandre 43e238292e [IMPROVE] - push the snapshot 2021-08-05 16:18:03 -03:00
AlexAlexandre d63ee28275 [IMPROVE] - removing a wrong //TODO 2021-08-05 16:10:27 -03:00
AlexAlexandre 078c361efc [IMPROVE] - fixing some errors appoint by jest 2021-08-04 16:19:08 -03:00
AlexAlexandre 1596f1621f [IMPROVE] - changing the types for interfaces, for our new patterns 2021-08-03 19:01:54 -03:00
AlexAlexandre 49b9cf436f [IMPROVE] - fixing some errors appoint by jest 2021-08-02 19:17:29 -03:00
AlexAlexandre 50a99fb846 [IMPROVE] - fixing some errors appoint by jest 2021-08-02 10:48:43 -03:00
Alex Junior 20d7b6a051 [IMPROVE] - creating an Message type file 2021-08-01 23:33:36 -03:00
Alex Junior 757347fa29 [IMPROVE] - creating an emoji type file 2021-08-01 23:11:20 -03:00
Alex Junior ca2e28f893 [IMPROVE] - creating an Avatar type file 2021-08-01 22:41:15 -03:00
Diego Mello 3f75741dee
Merge branch 'develop' into improvement.typescript-migration 2021-07-30 11:00:58 -03:00
AlexAlexandre 3e74f59ca3 [IMPROVE] - remove unused PropTypes 2021-07-29 15:53:36 -03:00
AlexAlexandre 91e6566542 [IMPROVE] - migrate KeyboardView presentation layer 2021-07-29 15:21:04 -03:00
AlexAlexandre 9d312bd9a6 [IMPROVE] - migrate UnreadBadge presentation layer 2021-07-29 15:15:09 -03:00
AlexAlexandre 601a45a194 [IMPROVE] - migrate RoomItem presentation layer 2021-07-29 15:12:41 -03:00
AlexAlexandre a533e5c827 [IMPROVE] - migrate ImageViewer presentation layer 2021-07-29 12:48:17 -03:00
AlexAlexandre 72df0aef7e [IMPROVE] - migrate serverItem presentation layer 2021-07-29 12:46:46 -03:00
AlexAlexandre 3f5f309643 [IMPROVE] - migrate directoryItem presentation layer 2021-07-29 12:45:32 -03:00
AlexAlexandre bacaf7fc33 [IMPROVE] - fix some notes during code review 2021-07-29 12:03:52 -03:00
AlexAlexandre c6e7826500 [IMPROVE] - fix some notes during code review 2021-07-28 14:43:32 -03:00
AlexAlexandre 107d4da851 [IMPROVE] - migrated forgot file 2021-07-28 14:42:18 -03:00
AlexAlexandre 9525786c0a removing unused proptypes import 2021-07-27 16:33:38 -03:00
AlexAlexandre 07741f0b98 Merge branch 'develop' into improvement.typescript-migration 2021-07-27 16:11:45 -03:00
AlexAlexandre 9179e90920 [IMPROVE] - migrating the last container files (finished) 2021-07-27 12:27:17 -03:00
AlexAlexandre 2c9f6f7f82 [IMPROVE] - migrating the last container files (in progress) 2021-07-27 00:13:43 -03:00
AlexAlexandre 11eaadde2d [IMPROVE] - migrating the UIKit container (finished) 2021-07-26 23:37:02 -03:00
AlexAlexandre e38e5880a8 [IMPROVE] - migrating the UIKit container (in progress) 2021-07-26 19:58:52 -03:00
AlexAlexandre 9a1b705471 [IMPROVE] - migrating the UIKit container (in progress) 2021-07-26 19:33:27 -03:00
AlexAlexandre 369b3f2ef6 [IMPROVE] - migrating the Passcode container 2021-07-26 14:35:37 -03:00
AlexAlexandre 60f659618d [IMPROVE] - migrating the RoomHeader container 2021-07-26 10:42:31 -03:00
AlexAlexandre e8b58323c7 [IMPROVE] - migrating the TwoFactor container 2021-07-23 16:33:40 -03:00
AlexAlexandre d5c2decfb9 [IMPROVE] - migrating the Status container 2021-07-23 16:27:52 -03:00
AlexAlexandre 4f65c1b371 [IMPROVE] - removing unused proptypes 2021-07-23 16:14:32 -03:00
AlexAlexandre 9e0154c436 [IMPROVE] - migrating the MessageBox container (in progress) 2021-07-23 15:54:46 -03:00
Alex Junior e9229e5097 [IMPROVE] - migrating the MessageBox container (in progress) 2021-07-23 01:15:16 -03:00
AlexAlexandre 0182853fdb [IMPROVE] - migrating the MessageBox container (in progress) 2021-07-22 23:59:46 -03:00
AlexAlexandre db11e664f6 [IMPROVE] - remove unused PropTypes 2021-07-22 23:14:21 -03:00
AlexAlexandre 024c744615 [IMPROVE] - migrating the message container (finished) 2021-07-22 22:59:16 -03:00
AlexAlexandre 6db54e9920 [IMPROVE] - migrating the message container (in progress) 2021-07-22 17:09:06 -03:00
AlexAlexandre 43c4234f73 [IMPROVE] fix some errors pointed out in LGTM 2021-07-22 15:59:48 -03:00
AlexAlexandre d3986689e5 [IMPROVE] fix the Reply message errors 2021-07-22 15:30:37 -03:00
AlexAlexandre 2b533f86e3 [IMPROVE] - migrating the message container (in progress) 2021-07-21 18:01:59 -03:00
AlexAlexandre f9b1e0eab4 [IMPROVE] remove unused proptypes 2021-07-21 14:51:26 -03:00
AlexAlexandre a557d7f5d2 [IMPROVE] migrate all markdown container files 2021-07-21 12:15:13 -03:00
AlexAlexandre 1c7ae63ac1 chore: adding some external modules for type 2021-07-21 12:14:26 -03:00
AlexAlexandre e0930466b4 [IMPROVE] migrate the List component 2021-07-21 10:54:39 -03:00
AlexAlexandre ee7ad68e92 Merge branch 'develop' into improvement.typescript-migration
# Conflicts:
#	package.json
#	yarn.lock
2021-07-20 17:57:17 -03:00
AlexAlexandre 4d07101a56 [IMPROVE] disable the "missing file extension" rule at eslintrc 2021-07-20 17:31:08 -03:00
AlexAlexandre b795b6fc5a [IMPROVE] migrating component the Header to reduce lint errors 2021-07-20 17:01:35 -03:00
AlexAlexandre 8ef11eab49 [IMPROVE] enable jsx compiler 2021-07-20 15:14:36 -03:00
AlexAlexandre d060edb05f [IMPROVE] keeping migrate some files and fix some lint errors 2021-07-19 16:52:45 -03:00
AlexAlexandre f5eee1cbaf [IMPROVE] migrate the emoji component 2021-07-19 12:06:45 -03:00
AlexAlexandre 66a4627fec [IMPROVE] fix typescript error “No overload matches this call” with some @ts-ignore for not changing the implementation 2021-07-16 16:31:28 -03:00
AlexAlexandre 19d8ab1a57 [IMPROVE] migrate EmojiPicker component (in progress) 2021-07-15 23:33:57 -03:00
AlexAlexandre b0ddb87c88 [IMPROVE] migrate BackgroundContainer component 2021-07-15 23:31:43 -03:00
AlexAlexandre a18b13c06b [IMPROVE] migrate Avatar component 2021-07-15 20:15:59 -03:00
AlexAlexandre 0354b49b08 [IMPROVE] trying remove an IGTM error 2021-07-15 18:56:37 -03:00
AlexAlexandre acdc2ce8f5 [IMPROVE] fix some typing erros 2021-07-15 18:46:31 -03:00
AlexAlexandre 802c7b2b28 [IMPROVE] migrate the ActionSheet component 2021-07-15 16:47:53 -03:00
AlexAlexandre 99b6c3d073 [IMPROVE] migrate the ActivityIndicator and the Button component 2021-07-15 16:47:30 -03:00
AlexAlexandre eadbb59e09 [IMPROVE] migrate all constants file 2021-07-15 11:57:48 -03:00
AlexAlexandre 584a16b20e Merge branch 'develop' into improvement.typescript-migration 2021-07-15 11:54:30 -03:00
AlexAlexandre b6577de7c2 [IMPROVE] migrate some navigation files, our index/share and low level files 2021-07-15 10:54:47 -03:00
AlexAlexandre fdcfd3e0ee [IMPROVE] Create a new file for export external modules 2021-07-15 10:48:57 -03:00
AlexAlexandre 8a1dc781eb [IMPROVE] migrate the HeaderButton component and the colors constant 2021-07-15 10:47:21 -03:00
AlexAlexandre 7bfe1fcadb chore: setup initial libs for using typescript 2021-07-14 11:39:26 -03:00
411 changed files with 6475 additions and 7430 deletions

View File

@ -2,12 +2,14 @@ module.exports = {
"settings": { "settings": {
"import/resolver": { "import/resolver": {
"node": { "node": {
"extensions": [".js", ".ios.js", ".android.js", ".native.js", ".tsx"] "extensions": [".js", ".ios.js", ".android.js", ".native.js", ".ts", ".tsx"],
} }
} }
}, },
"parser": "@babel/eslint-parser", "parser": "@babel/eslint-parser",
"extends": "airbnb", "extends": [
"@rocket.chat/eslint-config",
],
"parserOptions": { "parserOptions": {
"sourceType": "module", "sourceType": "module",
"ecmaVersion": 2017, "ecmaVersion": 2017,
@ -33,23 +35,40 @@ module.exports = {
"mocha": true "mocha": true
}, },
"rules": { "rules": {
"import/extensions": [
"error",
"ignorePackages",
{
"js": "warning",
"jsx": "warning",
"ts": "warning",
"tsx": "warning"
}
],
"react/jsx-filename-extension": [1, { "react/jsx-filename-extension": [1, {
"extensions": [".js", ".jsx"] "extensions": [".js", ".jsx", ".ts", ".tsx"]
}], }],
"react/require-default-props": [0], "react/require-default-props": [0],
"react/no-unused-prop-types": [2, { "ordered-imports": [0],
"skipShapeProps": true // "react/no-unused-prop-types": [2, {
}], // "skipShapeProps": true
// }],
"react/no-did-mount-set-state": 0, "react/no-did-mount-set-state": 0,
"react/no-multi-comp": [0], "react/no-multi-comp": [0],
"react/jsx-indent": [2, "tab"], "react/jsx-indent": [2, "tab"],
"react/jsx-indent-props": [2, "tab"], "react/jsx-indent-props": [2, "tab"],
"react/forbid-prop-types": 0, // "react/forbid-prop-types": 0,
"jsx-quotes": [2, "prefer-single"], "jsx-quotes": [2, "prefer-single"],
"jsx-a11y/href-no-hash": 0, "jsx-a11y/href-no-hash": 0,
"jsx-a11y/aria-role": 0, "jsx-a11y/aria-role": 0,
"import/prefer-default-export": 0, "import/prefer-default-export": 0,
"import/no-cycle": 0, "import/no-cycle": 0,
"import/order":[
"error",
{
"newlines-between": "ignore",
}
],
"camelcase": 0, "camelcase": 0,
"no-underscore-dangle": 0, "no-underscore-dangle": 0,
"no-return-assign": 0, "no-return-assign": 0,
@ -91,10 +110,7 @@ module.exports = {
"no-undef": 2, "no-undef": 2,
"no-unreachable": 2, "no-unreachable": 2,
"no-unused-expressions": 0, "no-unused-expressions": 0,
"no-unused-vars": [2, { "no-unused-vars": "off",
"vars": "all",
"args": "after-used"
}],
"max-len": 0, "max-len": 0,
"react/jsx-uses-vars": 2, "react/jsx-uses-vars": 2,
"no-void": 2, "no-void": 2,
@ -120,7 +136,7 @@ module.exports = {
"block-scoped-var": 2, "block-scoped-var": 2,
"curly": [2, "all"], "curly": [2, "all"],
"eqeqeq": [2, "allow-null"], "eqeqeq": [2, "allow-null"],
"new-cap": [2], "new-cap": "off",
"use-isnan": 2, "use-isnan": 2,
"valid-typeof": 2, "valid-typeof": 2,
"linebreak-style": 0, "linebreak-style": 0,
@ -136,23 +152,14 @@ module.exports = {
"react/jsx-one-expression-per-line": 0, "react/jsx-one-expression-per-line": 0,
"require-await": 2, "require-await": 2,
"func-names": 0, "func-names": 0,
"react/sort-comp": ["error", {
"order": [
"static-variables",
"static-methods",
"lifecycle",
"everything-else",
"render"
]
}],
"react/static-property-placement": [0], "react/static-property-placement": [0],
"arrow-parens": ["error", "as-needed", { requireForBlockBody: true }], "arrow-parens": ["error", "as-needed", { requireForBlockBody: true }],
"react/jsx-props-no-spreading": [1],
"react/jsx-curly-newline": [0], "react/jsx-curly-newline": [0],
"react/state-in-constructor": [0], "react/state-in-constructor": [0],
"no-async-promise-executor": [0], "no-async-promise-executor": [0],
"max-classes-per-file": [0], "max-classes-per-file": [0],
"no-multiple-empty-lines": [0] "no-multiple-empty-lines": [0],
"no-sequences": "off",
}, },
"globals": { "globals": {
"__DEV__": true "__DEV__": true
@ -173,6 +180,96 @@ module.exports = {
'no-await-in-loop': 0, 'no-await-in-loop': 0,
'no-restricted-syntax': 0 'no-restricted-syntax': 0
} }
},
{
"files": [
"**/*.ts",
"**/*.tsx"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"@rocket.chat/eslint-config"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2018,
"warnOnUnsupportedTypeScriptVersion": false,
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"legacyDecorators": true
}
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": [0],
"@typescript-eslint/ban-types": [0],
"func-call-spacing": "off",
"jsx-quotes": [
"error",
"prefer-single"
],
"indent": "off",
"no-return-assign": 0,
"no-dupe-class-members": "off",
"no-extra-parens": "off",
"no-spaced-func": "off",
"no-unused-vars": "off",
"no-useless-constructor": "off",
"no-use-before-define": "off",
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"react/jsx-no-undef": "error",
"react/jsx-fragments": [
"error",
"syntax"
],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/indent": [
"error",
"tab",
{
"SwitchCase": 1
}
],
"@typescript-eslint/no-extra-parens": [
"error",
"all",
{
"conditionalAssign": true,
"nestedBinaryExpressions": false,
"returnAssign": true,
"ignoreJSX": "all",
"enforceForArrowConditionals": false
}
],
"@typescript-eslint/no-dupe-class-members": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": ["error", {
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true
}],
"new-cap": "off",
},
"globals": {
"JSX": true,
},
"settings": {
"import/resolver": {
"node": {
"extensions": [
".js",
".ts",
".tsx"
]
}
},
}
} }
] ]
}; };

7
.prettierrc.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
bracketSpacing: false,
jsxBracketSameLine: true,
singleQuote: true,
trailingComma: 'all',
printWidth: 130,
};

View File

@ -4693,7 +4693,7 @@ exports[`Storyshots List pressable 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 92, "height": 92,
}, },
@ -6211,7 +6211,7 @@ exports[`Storyshots List with bigger font 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 69, "height": 69,
}, },
@ -6625,7 +6625,7 @@ exports[`Storyshots List with bigger font 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 69, "height": 69,
}, },
@ -7080,7 +7080,7 @@ exports[`Storyshots List with black theme 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 92, "height": 92,
}, },
@ -7494,7 +7494,7 @@ exports[`Storyshots List with black theme 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 92, "height": 92,
}, },
@ -7972,7 +7972,7 @@ exports[`Storyshots List with custom colors 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 92, "height": 92,
}, },
@ -8129,7 +8129,7 @@ exports[`Storyshots List with dark theme 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 92, "height": 92,
}, },
@ -8543,7 +8543,7 @@ exports[`Storyshots List with dark theme 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 92, "height": 92,
}, },
@ -10410,7 +10410,7 @@ exports[`Storyshots List with small font 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 36.800000000000004, "height": 36.800000000000004,
}, },
@ -10824,7 +10824,7 @@ exports[`Storyshots List with small font 1`] = `
"justifyContent": "center", "justifyContent": "center",
"paddingHorizontal": 12, "paddingHorizontal": 12,
}, },
false, undefined,
Object { Object {
"height": 36.800000000000004, "height": 36.800000000000004,
}, },

View File

@ -25,7 +25,7 @@ import com.android.build.OutputFile
* bundleAssetName: "index.android.bundle", * bundleAssetName: "index.android.bundle",
* *
* // the entry file for bundle generation. If none specified and * // the entry file for bundle generation. If none specified and
* // "index.android.js" exists, it will be used. Otherwise "index.js" is * // "index.android.js" exists, it will be used. Otherwise "index.tsx" is
* // default. Can be overridden with ENTRY_FILE environment variable. * // default. Can be overridden with ENTRY_FILE environment variable.
* entryFile: "index.android.js", * entryFile: "index.android.js",
* *

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -7,15 +6,12 @@ import { connect } from 'react-redux';
import Navigation from './lib/Navigation'; import Navigation from './lib/Navigation';
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation'; import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
import { import {
ROOT_LOADING, ROOT_OUTSIDE, ROOT_NEW_SERVER, ROOT_INSIDE, ROOT_SET_USERNAME ROOT_INSIDE, ROOT_LOADING, ROOT_NEW_SERVER, ROOT_OUTSIDE, ROOT_SET_USERNAME,
} from './actions/app'; } from './actions/app';
// Stacks // Stacks
import AuthLoadingView from './views/AuthLoadingView'; import AuthLoadingView from './views/AuthLoadingView';
// SetUsername Stack // SetUsername Stack
import SetUsernameView from './views/SetUsernameView'; import SetUsernameView from './views/SetUsernameView';
import OutsideStack from './stacks/OutsideStack'; import OutsideStack from './stacks/OutsideStack';
import InsideStack from './stacks/InsideStack'; import InsideStack from './stacks/InsideStack';
import MasterDetailStack from './stacks/MasterDetailStack'; import MasterDetailStack from './stacks/MasterDetailStack';
@ -35,7 +31,7 @@ const SetUsernameStack = () => (
// App // App
const Stack = createStackNavigator(); const Stack = createStackNavigator();
const App = React.memo(({ root, isMasterDetail }) => { const App = React.memo(({ root, isMasterDetail }: {root: string, isMasterDetail: boolean}) => {
if (!root) { if (!root) {
return null; return null;
} }
@ -100,15 +96,10 @@ const App = React.memo(({ root, isMasterDetail }) => {
</NavigationContainer> </NavigationContainer>
); );
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
root: state.app.root, root: state.app.root,
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail,
}); });
App.propTypes = {
root: PropTypes.string,
isMasterDetail: PropTypes.bool
};
const AppContainer = connect(mapStateToProps)(App); const AppContainer = connect(mapStateToProps)(App);
export default AppContainer; export default AppContainer;

View File

@ -4,7 +4,7 @@ const FAILURE = 'FAILURE';
const defaultTypes = [REQUEST, SUCCESS, FAILURE]; const defaultTypes = [REQUEST, SUCCESS, FAILURE];
function createRequestTypes(base, types = defaultTypes) { function createRequestTypes(base, types = defaultTypes) {
const res = {}; const res = {};
types.forEach(type => (res[type] = `${ base }_${ type }`)); types.forEach(type => res[type] = `${ base }_${ type }`);
return res; return res;
} }

View File

@ -125,15 +125,15 @@ const keyCommands = [
discoverabilityTitle: I18n.t('Add_server') discoverabilityTitle: I18n.t('Add_server')
}, },
// Refers to select rooms on list // Refers to select rooms on list
...([1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({ ...[1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({
input: `${ value }`, input: `${ value }`,
modifierFlags: constants.keyModifierCommand modifierFlags: constants.keyModifierCommand
}))), })),
// Refers to select servers on list // Refers to select servers on list
...([1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({ ...[1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({
input: `${ value }`, input: `${ value }`,
modifierFlags: constants.keyModifierCommand | constants.keyModifierAlternate modifierFlags: constants.keyModifierCommand | constants.keyModifierAlternate
}))) }))
]; ];
export const setKeyCommands = () => KeyCommands.setKeyCommands(keyCommands); export const setKeyCommands = () => KeyCommands.setKeyCommands(keyCommands);

View File

@ -1,14 +1,14 @@
export const STATUS_COLORS = { export const STATUS_COLORS: any = {
online: '#2de0a5', online: '#2de0a5',
busy: '#f5455c', busy: '#f5455c',
away: '#ffd21f', away: '#ffd21f',
offline: '#cbced1', offline: '#cbced1',
loading: '#9ea2a8' loading: '#9ea2a8',
}; };
export const SWITCH_TRACK_COLOR = { export const SWITCH_TRACK_COLOR = {
false: '#f5455c', false: '#f5455c',
true: '#2de0a5' true: '#2de0a5',
}; };
const mentions = { const mentions = {
@ -16,10 +16,10 @@ const mentions = {
tunreadColor: '#1d74f5', tunreadColor: '#1d74f5',
mentionMeColor: '#F5455C', mentionMeColor: '#F5455C',
mentionGroupColor: '#F38C39', mentionGroupColor: '#F38C39',
mentionOtherColor: '#F3BE08' mentionOtherColor: '#F3BE08',
}; };
export const themes = { export const themes: any = {
light: { light: {
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
focusedBackground: '#ffffff', focusedBackground: '#ffffff',
@ -65,7 +65,7 @@ export const themes = {
previewBackground: '#1F2329', previewBackground: '#1F2329',
previewTintColor: '#ffffff', previewTintColor: '#ffffff',
backdropOpacity: 0.3, backdropOpacity: 0.3,
...mentions ...mentions,
}, },
dark: { dark: {
backgroundColor: '#030b1b', backgroundColor: '#030b1b',
@ -112,7 +112,7 @@ export const themes = {
previewBackground: '#030b1b', previewBackground: '#030b1b',
previewTintColor: '#ffffff', previewTintColor: '#ffffff',
backdropOpacity: 0.9, backdropOpacity: 0.9,
...mentions ...mentions,
}, },
black: { black: {
backgroundColor: '#000000', backgroundColor: '#000000',
@ -159,6 +159,6 @@ export const themes = {
previewBackground: '#000000', previewBackground: '#000000',
previewTintColor: '#ffffff', previewTintColor: '#ffffff',
backdropOpacity: 0.9, backdropOpacity: 0.9,
...mentions ...mentions,
} },
}; };

View File

@ -1,5 +1,5 @@
export default { export default {
SENT: 0, SENT: 0,
TEMP: 1, TEMP: 1,
ERROR: 2 ERROR: 2,
}; };

View File

@ -1,206 +1,206 @@
export default { export default {
Accounts_AllowEmailChange: { Accounts_AllowEmailChange: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_AllowPasswordChange: { Accounts_AllowPasswordChange: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_AllowRealNameChange: { Accounts_AllowRealNameChange: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_AllowUserAvatarChange: { Accounts_AllowUserAvatarChange: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_AllowUserProfileChange: { Accounts_AllowUserProfileChange: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_AllowUserStatusMessageChange: { Accounts_AllowUserStatusMessageChange: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_AllowUsernameChange: { Accounts_AllowUsernameChange: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_AvatarBlockUnauthenticatedAccess: { Accounts_AvatarBlockUnauthenticatedAccess: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_CustomFields: { Accounts_CustomFields: {
type: 'valueAsString' type: 'valueAsString',
}, },
Accounts_EmailOrUsernamePlaceholder: { Accounts_EmailOrUsernamePlaceholder: {
type: 'valueAsString' type: 'valueAsString',
}, },
Accounts_EmailVerification: { Accounts_EmailVerification: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_NamePlaceholder: { Accounts_NamePlaceholder: {
type: 'valueAsString' type: 'valueAsString',
}, },
Accounts_PasswordPlaceholder: { Accounts_PasswordPlaceholder: {
type: 'valueAsString' type: 'valueAsString',
}, },
Accounts_PasswordReset: { Accounts_PasswordReset: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_RegistrationForm: { Accounts_RegistrationForm: {
type: 'valueAsString' type: 'valueAsString',
}, },
Accounts_RegistrationForm_LinkReplacementText: { Accounts_RegistrationForm_LinkReplacementText: {
type: 'valueAsString' type: 'valueAsString',
}, },
Accounts_ShowFormLogin: { Accounts_ShowFormLogin: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_ManuallyApproveNewUsers: { Accounts_ManuallyApproveNewUsers: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
API_Use_REST_For_DDP_Calls: { API_Use_REST_For_DDP_Calls: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_iframe_enabled: { Accounts_iframe_enabled: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_Iframe_api_url: { Accounts_Iframe_api_url: {
type: 'valueAsString' type: 'valueAsString',
}, },
Accounts_Iframe_api_method: { Accounts_Iframe_api_method: {
type: 'valueAsString' type: 'valueAsString',
}, },
CROWD_Enable: { CROWD_Enable: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
DirectMesssage_maxUsers: { DirectMesssage_maxUsers: {
type: 'valueAsNumber' type: 'valueAsNumber',
}, },
E2E_Enable: { E2E_Enable: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_Directory_DefaultView: { Accounts_Directory_DefaultView: {
type: 'valueAsString' type: 'valueAsString',
}, },
FEDERATION_Enabled: { FEDERATION_Enabled: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Hide_System_Messages: { Hide_System_Messages: {
type: 'valueAsArray' type: 'valueAsArray',
}, },
LDAP_Enable: { LDAP_Enable: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Livechat_request_comment_when_closing_conversation: { Livechat_request_comment_when_closing_conversation: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Jitsi_Enabled: { Jitsi_Enabled: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Jitsi_SSL: { Jitsi_SSL: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Jitsi_Domain: { Jitsi_Domain: {
type: 'valueAsString' type: 'valueAsString',
}, },
Jitsi_Enabled_TokenAuth: { Jitsi_Enabled_TokenAuth: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Jitsi_URL_Room_Hash: { Jitsi_URL_Room_Hash: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Jitsi_URL_Room_Prefix: { Jitsi_URL_Room_Prefix: {
type: 'valueAsString' type: 'valueAsString',
}, },
Message_AllowDeleting: { Message_AllowDeleting: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Message_AllowDeleting_BlockDeleteInMinutes: { Message_AllowDeleting_BlockDeleteInMinutes: {
type: 'valueAsNumber' type: 'valueAsNumber',
}, },
Message_AllowEditing: { Message_AllowEditing: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Message_AllowEditing_BlockEditInMinutes: { Message_AllowEditing_BlockEditInMinutes: {
type: 'valueAsNumber' type: 'valueAsNumber',
}, },
Message_AllowPinning: { Message_AllowPinning: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Message_AllowStarring: { Message_AllowStarring: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Message_AudioRecorderEnabled: { Message_AudioRecorderEnabled: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Message_GroupingPeriod: { Message_GroupingPeriod: {
type: 'valueAsNumber' type: 'valueAsNumber',
}, },
Message_TimeFormat: { Message_TimeFormat: {
type: 'valueAsString' type: 'valueAsString',
}, },
Message_TimeAndDateFormat: { Message_TimeAndDateFormat: {
type: 'valueAsString' type: 'valueAsString',
}, },
Site_Name: { Site_Name: {
type: 'valueAsString' type: 'valueAsString',
}, },
Site_Url: { Site_Url: {
type: 'valueAsString' type: 'valueAsString',
}, },
Store_Last_Message: { Store_Last_Message: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
uniqueID: { uniqueID: {
type: 'valueAsString' type: 'valueAsString',
}, },
UI_Allow_room_names_with_special_chars: { UI_Allow_room_names_with_special_chars: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
UI_Use_Real_Name: { UI_Use_Real_Name: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Assets_favicon_512: { Assets_favicon_512: {
type: null type: null,
}, },
Message_Read_Receipt_Enabled: { Message_Read_Receipt_Enabled: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Message_Read_Receipt_Store_Users: { Message_Read_Receipt_Store_Users: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Threads_enabled: { Threads_enabled: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
FileUpload_MediaTypeWhiteList: { FileUpload_MediaTypeWhiteList: {
type: 'valueAsString' type: 'valueAsString',
}, },
FileUpload_MaxFileSize: { FileUpload_MaxFileSize: {
type: 'valueAsNumber' type: 'valueAsNumber',
}, },
API_Gitlab_URL: { API_Gitlab_URL: {
type: 'valueAsString' type: 'valueAsString',
}, },
AutoTranslate_Enabled: { AutoTranslate_Enabled: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
CAS_enabled: { CAS_enabled: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
CAS_login_url: { CAS_login_url: {
type: 'valueAsString' type: 'valueAsString',
}, },
Force_Screen_Lock: { Force_Screen_Lock: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Force_Screen_Lock_After: { Force_Screen_Lock_After: {
type: 'valueAsNumber' type: 'valueAsNumber',
}, },
Allow_Save_Media_to_Gallery: { Allow_Save_Media_to_Gallery: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Accounts_AllowInvisibleStatusOption: { Accounts_AllowInvisibleStatusOption: {
type: 'valueAsString' type: 'valueAsString',
}, },
Jitsi_Enable_Teams: { Jitsi_Enable_Teams: {
type: 'valueAsBoolean' type: 'valueAsBoolean',
}, },
Jitsi_Enable_Channels: { Jitsi_Enable_Channels: {
type: 'valuesAsBoolean' type: 'valuesAsBoolean',
} },
}; };

View File

@ -1,23 +1,17 @@
import React, { import React, {
forwardRef,
isValidElement,
useCallback,
useEffect,
useImperativeHandle,
useRef, useRef,
useState, useState,
useEffect,
forwardRef,
useImperativeHandle,
useCallback,
isValidElement
} from 'react'; } from 'react';
import PropTypes from 'prop-types';
import { Keyboard, Text } from 'react-native'; import { Keyboard, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { TapGestureHandler, State } from 'react-native-gesture-handler'; import { State, TapGestureHandler } from 'react-native-gesture-handler';
import ScrollBottomSheet from 'react-native-scroll-bottom-sheet'; import ScrollBottomSheet from 'react-native-scroll-bottom-sheet';
import Animated, { import Animated, { Easing, Extrapolate, Value, interpolate } from 'react-native-reanimated';
Extrapolate,
interpolate,
Value,
Easing
} from 'react-native-reanimated';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
import { useBackHandler } from '@react-native-community/hooks'; import { useBackHandler } from '@react-native-community/hooks';
@ -26,12 +20,19 @@ import { Handle } from './Handle';
import { Button } from './Button'; import { Button } from './Button';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import styles, { ITEM_HEIGHT } from './styles'; import styles, { ITEM_HEIGHT } from './styles';
import { isTablet, isIOS } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
import * as List from '../List'; import * as List from '../List';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { useOrientation, useDimensions } from '../../dimensions'; import { IDimensionsContextProps, useDimensions, useOrientation } from '../../dimensions';
const getItemLayout = (data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }); interface IActionSheetData {
options: any;
headerHeight?: number;
hasCancel?: boolean;
customHeader: any;
}
const getItemLayout = (data: any, index: number) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index });
const HANDLE_HEIGHT = isIOS ? 40 : 56; const HANDLE_HEIGHT = isIOS ? 40 : 56;
const MAX_SNAP_HEIGHT = 16; const MAX_SNAP_HEIGHT = 16;
@ -42,20 +43,20 @@ const ANIMATION_DURATION = 250;
const ANIMATION_CONFIG = { const ANIMATION_CONFIG = {
duration: ANIMATION_DURATION, duration: ANIMATION_DURATION,
// https://easings.net/#easeInOutCubic // https://easings.net/#easeInOutCubic
easing: Easing.bezier(0.645, 0.045, 0.355, 1.0) easing: Easing.bezier(0.645, 0.045, 0.355, 1.0),
}; };
const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => { const ActionSheet = React.memo(forwardRef(({ children, theme }: {children: JSX.Element; theme: string}, ref) => {
const bottomSheetRef = useRef(); const bottomSheetRef: any = useRef();
const [data, setData] = useState({}); const [data, setData] = useState<IActionSheetData>({} as IActionSheetData);
const [isVisible, setVisible] = useState(false); const [isVisible, setVisible] = useState(false);
const { height } = useDimensions(); const { height }: Partial<IDimensionsContextProps> = useDimensions();
const { isLandscape } = useOrientation(); const { isLandscape } = useOrientation();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const maxSnap = Math.max( const maxSnap = Math.max(
(
height height!
// Items height // Items height
- (ITEM_HEIGHT * (data?.options?.length || 0)) - (ITEM_HEIGHT * (data?.options?.length || 0))
// Handle height // Handle height
@ -66,8 +67,8 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
- insets.bottom - insets.bottom
// Cancel button height // Cancel button height
- (data?.hasCancel ? CANCEL_HEIGHT : 0) - (data?.hasCancel ? CANCEL_HEIGHT : 0)
), ,
MAX_SNAP_HEIGHT MAX_SNAP_HEIGHT,
); );
/* /*
@ -77,7 +78,7 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
* we'll provide more one snap * we'll provide more one snap
* that point 50% of the whole screen * that point 50% of the whole screen
*/ */
const snaps = (height - maxSnap > height * 0.6) && !isLandscape ? [maxSnap, height * 0.5, height] : [maxSnap, height]; const snaps: any = (height! - maxSnap > height! * 0.6) && !isLandscape ? [maxSnap, height! * 0.5, height] : [maxSnap, height];
const openedSnapIndex = snaps.length > 2 ? 1 : 0; const openedSnapIndex = snaps.length > 2 ? 1 : 0;
const closedSnapIndex = snaps.length - 1; const closedSnapIndex = snaps.length - 1;
@ -87,12 +88,12 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
bottomSheetRef.current?.snapTo(closedSnapIndex); bottomSheetRef.current?.snapTo(closedSnapIndex);
}; };
const show = (options) => { const show = (options: any) => {
setData(options); setData(options);
toggleVisible(); toggleVisible();
}; };
const onBackdropPressed = ({ nativeEvent }) => { const onBackdropPressed = ({ nativeEvent }: any) => {
if (nativeEvent.oldState === State.ACTIVE) { if (nativeEvent.oldState === State.ACTIVE) {
hide(); hide();
} }
@ -120,7 +121,7 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
showActionSheet: show, showActionSheet: show,
hideActionSheet: hide hideActionSheet: hide,
})); }));
const renderHandle = useCallback(() => ( const renderHandle = useCallback(() => (
@ -128,7 +129,7 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
<Handle theme={theme} /> <Handle theme={theme} />
{isValidElement(data?.customHeader) ? data.customHeader : null} {isValidElement(data?.customHeader) ? data.customHeader : null}
</> </>
)); ), [theme, data]);
const renderFooter = useCallback(() => (data?.hasCancel ? ( const renderFooter = useCallback(() => (data?.hasCancel ? (
<Button <Button
@ -140,15 +141,15 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
{I18n.t('Cancel')} {I18n.t('Cancel')}
</Text> </Text>
</Button> </Button>
) : null)); ) : null), [theme, data, hide]);
const renderItem = useCallback(({ item }) => <Item item={item} hide={hide} theme={theme} />); const renderItem = useCallback(({ item }) => <Item item={item} hide={hide} theme={theme} />, []);
const animatedPosition = React.useRef(new Value(0)); const animatedPosition = React.useRef(new Value(0));
const opacity = interpolate(animatedPosition.current, { const opacity = interpolate(animatedPosition.current, {
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity], outputRange: [0, themes[theme].backdropOpacity],
extrapolate: Extrapolate.CLAMP extrapolate: Extrapolate.CLAMP,
}); });
return ( return (
@ -163,8 +164,8 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
styles.backdrop, styles.backdrop,
{ {
backgroundColor: themes[theme].backdropColor, backgroundColor: themes[theme].backdropColor,
opacity opacity,
} },
]} ]}
/> />
</TapGestureHandler> </TapGestureHandler>
@ -175,18 +176,18 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
snapPoints={snaps} snapPoints={snaps}
initialSnapIndex={closedSnapIndex} initialSnapIndex={closedSnapIndex}
renderHandle={renderHandle} renderHandle={renderHandle}
onSettle={index => (index === closedSnapIndex) && toggleVisible()} onSettle={(index) => (index === closedSnapIndex) && toggleVisible()}
animatedPosition={animatedPosition.current} animatedPosition={animatedPosition.current}
containerStyle={[ containerStyle={[
styles.container, styles.container,
{ backgroundColor: themes[theme].focusedBackground }, { backgroundColor: themes[theme].focusedBackground },
(isLandscape || isTablet) && styles.bottomSheet (isLandscape || isTablet) && styles.bottomSheet,
]} ] as any}
animationConfig={ANIMATION_CONFIG} animationConfig={ANIMATION_CONFIG}
// FlatList props // FlatList props
data={data?.options} data={data?.options}
renderItem={renderItem} renderItem={renderItem}
keyExtractor={item => item.title} keyExtractor={(item: any) => item.title}
style={{ backgroundColor: themes[theme].focusedBackground }} style={{ backgroundColor: themes[theme].focusedBackground }}
contentContainerStyle={styles.content} contentContainerStyle={styles.content}
ItemSeparatorComponent={List.Separator} ItemSeparatorComponent={List.Separator}
@ -200,9 +201,5 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
</> </>
); );
})); }));
ActionSheet.propTypes = {
children: PropTypes.node,
theme: PropTypes.string
};
export default ActionSheet; export default ActionSheet;

View File

@ -1,15 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native'; import { View } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
export const Handle = React.memo(({ theme }) => ( export const Handle = React.memo(({ theme }: {theme: string}) => (
<View style={[styles.handle, { backgroundColor: themes[theme].focusedBackground }]} testID='action-sheet-handle'> <View style={[styles.handle, { backgroundColor: themes[theme].focusedBackground }]} testID='action-sheet-handle'>
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} /> <View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
</View> </View>
)); ));
Handle.propTypes = {
theme: PropTypes.string
};

View File

@ -1,13 +1,25 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import styles from './styles';
import { Button } from './Button'; import { Button } from './Button';
import styles from './styles';
export const Item = React.memo(({ item, hide, theme }) => { interface IActionSheetItem {
item: {
title: string;
icon: string;
danger: boolean;
testID: string;
onPress(): void;
right: Function;
};
theme: string
hide(): void;
}
export const Item = React.memo(({ item, hide, theme }: IActionSheetItem) => {
const onPress = () => { const onPress = () => {
hide(); hide();
item?.onPress(); item?.onPress();
@ -37,15 +49,3 @@ export const Item = React.memo(({ item, hide, theme }) => {
</Button> </Button>
); );
}); });
Item.propTypes = {
item: PropTypes.shape({
title: PropTypes.string,
icon: PropTypes.string,
danger: PropTypes.bool,
onPress: PropTypes.func,
right: PropTypes.func,
testID: PropTypes.string
}),
hide: PropTypes.func,
theme: PropTypes.string
};

View File

@ -1,45 +0,0 @@
import React, { useRef, useContext, forwardRef } from 'react';
import PropTypes from 'prop-types';
import ActionSheet from './ActionSheet';
import { useTheme } from '../../theme';
const context = React.createContext({
showActionSheet: () => {},
hideActionSheet: () => {}
});
export const useActionSheet = () => useContext(context);
const { Provider, Consumer } = context;
export const withActionSheet = Component => forwardRef((props, ref) => (
<Consumer>
{contexts => <Component {...props} {...contexts} ref={ref} />}
</Consumer>
));
export const ActionSheetProvider = React.memo(({ children }) => {
const ref = useRef();
const { theme } = useTheme();
const getContext = () => ({
showActionSheet: (options) => {
ref.current?.showActionSheet(options);
},
hideActionSheet: () => {
ref.current?.hideActionSheet();
}
});
return (
<Provider value={getContext()}>
<ActionSheet ref={ref} theme={theme}>
{children}
</ActionSheet>
</Provider>
);
});
ActionSheetProvider.propTypes = {
children: PropTypes.node
};

View File

@ -0,0 +1,46 @@
import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
import ActionSheet from './ActionSheet';
import { useTheme } from '../../theme';
interface IActionSheetProvider {
Provider: any;
Consumer: any;
}
const context: IActionSheetProvider = React.createContext({
showActionSheet: () => {},
hideActionSheet: () => {},
});
export const useActionSheet = () => useContext(context);
const { Provider, Consumer } = context;
export const withActionSheet = (Component: React.FC) => forwardRef((props: any, ref: ForwardedRef<any>) => (
<Consumer>
{(contexts: any) => <Component {...props} {...contexts} ref={ref} />}
</Consumer>
));
export const ActionSheetProvider = React.memo(({ children }: {children: JSX.Element}) => {
const ref: ForwardedRef<any> = useRef();
const { theme }: any = useTheme();
const getContext = () => ({
showActionSheet: (options: any) => {
ref.current?.showActionSheet(options);
},
hideActionSheet: () => {
ref.current?.hideActionSheet();
},
});
return (
<Provider value={getContext()}>
<ActionSheet ref={ref} theme={theme}>
{children}
</ActionSheet>
</Provider>
);
});

View File

@ -8,46 +8,46 @@ export default StyleSheet.create({
container: { container: {
overflow: 'hidden', overflow: 'hidden',
borderTopLeftRadius: 16, borderTopLeftRadius: 16,
borderTopRightRadius: 16 borderTopRightRadius: 16,
}, },
item: { item: {
paddingHorizontal: 16, paddingHorizontal: 16,
height: ITEM_HEIGHT, height: ITEM_HEIGHT,
alignItems: 'center', alignItems: 'center',
flexDirection: 'row' flexDirection: 'row',
}, },
separator: { separator: {
marginHorizontal: 16 marginHorizontal: 16,
}, },
content: { content: {
paddingTop: 16 paddingTop: 16,
}, },
titleContainer: { titleContainer: {
flex: 1 flex: 1,
}, },
title: { title: {
fontSize: 16, fontSize: 16,
marginLeft: 16, marginLeft: 16,
...sharedStyles.textRegular ...sharedStyles.textRegular,
}, },
handle: { handle: {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
paddingBottom: 8 paddingBottom: 8,
}, },
handleIndicator: { handleIndicator: {
width: 40, width: 40,
height: 4, height: 4,
borderRadius: 4, borderRadius: 4,
margin: 8 margin: 8,
}, },
backdrop: { backdrop: {
...StyleSheet.absoluteFillObject ...StyleSheet.absoluteFillObject,
}, },
bottomSheet: { bottomSheet: {
width: '50%', width: '50%',
alignSelf: 'center', alignSelf: 'center',
left: '25%' left: '25%',
}, },
button: { button: {
marginHorizontal: 16, marginHorizontal: 16,
@ -55,14 +55,14 @@ export default StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
height: ITEM_HEIGHT, height: ITEM_HEIGHT,
borderRadius: 2, borderRadius: 2,
marginBottom: 12 marginBottom: 12,
}, },
text: { text: {
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium, ...sharedStyles.textMedium,
...sharedStyles.textAlignCenter ...sharedStyles.textAlignCenter,
}, },
rightContainer: { rightContainer: {
paddingLeft: 12 paddingLeft: 12,
} },
}); });

View File

@ -1,12 +1,20 @@
import React from 'react'; import React from 'react';
import { ActivityIndicator, StyleSheet } from 'react-native'; import { ActivityIndicator, ActivityIndicatorProps, StyleSheet } from 'react-native';
import { PropTypes } from 'prop-types';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
type TTheme = 'light' | 'dark' | 'black' | string;
interface IActivityIndicator extends ActivityIndicatorProps{
theme?: TTheme;
absolute?: boolean;
props?: object;
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
indicator: { indicator: {
padding: 16, padding: 16,
flex: 1 flex: 1,
}, },
absolute: { absolute: {
position: 'absolute', position: 'absolute',
@ -15,11 +23,11 @@ const styles = StyleSheet.create({
top: 0, top: 0,
bottom: 0, bottom: 0,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
} },
}); });
const RCActivityIndicator = ({ theme, absolute, ...props }) => ( const RCActivityIndicator = ({ theme = 'light', absolute, ...props }: IActivityIndicator) => (
<ActivityIndicator <ActivityIndicator
style={[styles.indicator, absolute && styles.absolute]} style={[styles.indicator, absolute && styles.absolute]}
color={themes[theme].auxiliaryText} color={themes[theme].auxiliaryText}
@ -27,14 +35,4 @@ const RCActivityIndicator = ({ theme, absolute, ...props }) => (
/> />
); );
RCActivityIndicator.propTypes = {
theme: PropTypes.string,
absolute: PropTypes.bool,
props: PropTypes.object
};
RCActivityIndicator.defaultProps = {
theme: 'light'
};
export default RCActivityIndicator; export default RCActivityIndicator;

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View, Text } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
@ -10,25 +9,21 @@ import I18n from '../i18n';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-end' justifyContent: 'flex-end',
}, },
text: { text: {
...sharedStyles.textRegular, ...sharedStyles.textRegular,
fontSize: 13 fontSize: 13,
}, },
bold: { bold: {
...sharedStyles.textSemibold ...sharedStyles.textSemibold,
} },
}); });
const AppVersion = React.memo(({ theme }) => ( const AppVersion = React.memo(({ theme }: {theme: string}) => (
<View style={styles.container}> <View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{I18n.t('Version_no', { version: '' })}<Text style={styles.bold}>{getReadableVersion}</Text></Text> <Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{I18n.t('Version_no', { version: '' })}<Text style={styles.bold}>{getReadableVersion}</Text></Text>
</View> </View>
)); ));
AppVersion.propTypes = {
theme: PropTypes.string
};
export default AppVersion; export default AppVersion;

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native'; import { View } from 'react-native';
import FastImage from '@rocket.chat/react-native-fast-image'; import FastImage from '@rocket.chat/react-native-fast-image';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
@ -7,15 +6,12 @@ import { settings as RocketChatSettings } from '@rocket.chat/sdk';
import { avatarURL } from '../../utils/avatar'; import { avatarURL } from '../../utils/avatar';
import Emoji from '../markdown/Emoji'; import Emoji from '../markdown/Emoji';
import { IAvatar } from './interfaces';
const Avatar = React.memo(({ const Avatar = React.memo(({
text,
size,
server, server,
borderRadius,
style, style,
avatar, avatar,
type,
children, children,
user, user,
onPress, onPress,
@ -26,8 +22,12 @@ const Avatar = React.memo(({
isStatic, isStatic,
rid, rid,
blockUnauthenticatedAccess, blockUnauthenticatedAccess,
serverVersion serverVersion,
}) => { text,
size = 25,
borderRadius = 4,
type = 'd',
}: Partial<IAvatar>) => {
if ((!text && !avatar && !emoji && !rid) || !server) { if ((!text && !avatar && !emoji && !rid) || !server) {
return null; return null;
} }
@ -35,7 +35,7 @@ const Avatar = React.memo(({
const avatarStyle = { const avatarStyle = {
width: size, width: size,
height: size, height: size,
borderRadius borderRadius,
}; };
let image; let image;
@ -63,7 +63,7 @@ const Avatar = React.memo(({
avatarETag, avatarETag,
serverVersion, serverVersion,
rid, rid,
blockUnauthenticatedAccess blockUnauthenticatedAccess,
}); });
} }
@ -73,7 +73,7 @@ const Avatar = React.memo(({
source={{ source={{
uri, uri,
headers: RocketChatSettings.customHeaders, headers: RocketChatSettings.customHeaders,
priority: FastImage.priority.high priority: FastImage.priority.high,
}} }}
/> />
); );
@ -96,35 +96,4 @@ const Avatar = React.memo(({
); );
}); });
Avatar.propTypes = {
server: PropTypes.string,
style: PropTypes.any,
text: PropTypes.string,
avatar: PropTypes.string,
emoji: PropTypes.string,
size: PropTypes.number,
borderRadius: PropTypes.number,
type: PropTypes.string,
children: PropTypes.object,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
}),
theme: PropTypes.string,
onPress: PropTypes.func,
getCustomEmoji: PropTypes.func,
avatarETag: PropTypes.string,
isStatic: PropTypes.bool,
rid: PropTypes.string,
blockUnauthenticatedAccess: PropTypes.bool,
serverVersion: PropTypes.string
};
Avatar.defaultProps = {
text: '',
size: 25,
type: 'd',
borderRadius: 4
};
export default Avatar; export default Avatar;

View File

@ -1,27 +1,23 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import database from '../../lib/database'; import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import Avatar from './Avatar'; import Avatar from './Avatar';
import { IAvatar } from './interfaces';
class AvatarContainer extends React.Component { class AvatarContainer extends React.Component<Partial<IAvatar>, any> {
static propTypes = { private mounted: boolean;
rid: PropTypes.string,
text: PropTypes.string, private subscription!: any;
type: PropTypes.string,
blockUnauthenticatedAccess: PropTypes.bool,
serverVersion: PropTypes.string
};
static defaultProps = { static defaultProps = {
text: '', text: '',
type: 'd' type: 'd',
}; };
constructor(props) { constructor(props: Partial<IAvatar>) {
super(props); super(props);
this.mounted = false; this.mounted = false;
this.state = { avatarETag: '' }; this.state = { avatarETag: '' };
@ -32,7 +28,7 @@ class AvatarContainer extends React.Component {
this.mounted = true; this.mounted = true;
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: any) {
const { text, type } = this.props; const { text, type } = this.props;
if (prevProps.text !== text || prevProps.type !== type) { if (prevProps.text !== text || prevProps.type !== type) {
this.init(); this.init();
@ -50,7 +46,7 @@ class AvatarContainer extends React.Component {
return type === 'd'; return type === 'd';
} }
init = async() => { init = async () => {
const db = database.active; const db = database.active;
const usersCollection = db.get('users'); const usersCollection = db.get('users');
const subsCollection = db.get('subscriptions'); const subsCollection = db.get('subscriptions');
@ -59,7 +55,7 @@ class AvatarContainer extends React.Component {
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;
@ -71,11 +67,12 @@ class AvatarContainer extends React.Component {
if (record) { if (record) {
const observable = record.observe(); const observable = record.observe();
this.subscription = observable.subscribe((r) => { this.subscription = observable.subscribe((r: any) => {
const { avatarETag } = r; const { avatarETag } = r;
if (this.mounted) { if (this.mounted) {
this.setState({ avatarETag }); this.setState({ avatarETag });
} else { } else {
// @ts-ignore
this.state.avatarETag = avatarETag; this.state.avatarETag = avatarETag;
} }
}); });
@ -95,13 +92,13 @@ class AvatarContainer extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
user: getUserSelector(state), user: getUserSelector(state),
server: state.share.server.server || state.server.server, server: state.share.server.server || state.server.server,
serverVersion: state.share.server.version || state.server.version, serverVersion: state.share.server.version || state.server.version,
blockUnauthenticatedAccess: blockUnauthenticatedAccess:
state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess
?? state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? state.settings.Accounts_AvatarBlockUnauthenticatedAccess
?? true ?? true,
}); });
export default connect(mapStateToProps)(AvatarContainer); export default connect(mapStateToProps)(AvatarContainer);

View File

@ -0,0 +1,23 @@
export interface IAvatar {
server: string;
style: any,
text: 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;
rid: string;
blockUnauthenticatedAccess: boolean;
serverVersion: string;
}

View File

@ -2,9 +2,9 @@
import React from 'react'; import React from 'react';
import { storiesOf } from '@storybook/react-native'; import { storiesOf } from '@storybook/react-native';
import BackgroundContainer from '.';
import { ThemeContext } from '../../theme'; import { ThemeContext } from '../../theme';
import { longText } from '../../../storybook/utils'; import { longText } from '../../../storybook/utils';
import BackgroundContainer from '.';
const stories = storiesOf('BackgroundContainer', module); const stories = storiesOf('BackgroundContainer', module);

View File

@ -1,21 +1,24 @@
import React from 'react'; import React from 'react';
import { import { ActivityIndicator, ImageBackground, StyleSheet, Text, View } from 'react-native';
ImageBackground, StyleSheet, Text, View, ActivityIndicator
} from 'react-native';
import PropTypes from 'prop-types';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
interface IBackgroundContainer {
text: string;
theme: string;
loading: boolean;
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1 flex: 1,
}, },
image: { image: {
width: '100%', width: '100%',
height: '100%', height: '100%',
position: 'absolute' position: 'absolute',
}, },
text: { text: {
position: 'absolute', position: 'absolute',
@ -25,21 +28,17 @@ const styles = StyleSheet.create({
fontSize: 16, fontSize: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
...sharedStyles.textRegular, ...sharedStyles.textRegular,
...sharedStyles.textAlignCenter ...sharedStyles.textAlignCenter,
} },
}); });
const BackgroundContainer = ({ theme, text, loading }) => ( const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => (
<View style={styles.container}> <View style={styles.container}>
<ImageBackground source={{ uri: `message_empty_${ theme }` }} style={styles.image} /> <ImageBackground source={{ uri: `message_empty_${ theme }` }} style={styles.image} />
{text ? <Text style={[styles.text, { color: themes[theme].auxiliaryTintColor }]}>{text}</Text> : null} {text ? <Text style={[styles.text, { color: themes[theme].auxiliaryTintColor }]}>{text}</Text> : null}
{/* @ts-ignore*/}
{loading ? <ActivityIndicator style={[styles.text, { color: themes[theme].auxiliaryTintColor }]} /> : null} {loading ? <ActivityIndicator style={[styles.text, { color: themes[theme].auxiliaryTintColor }]} /> : null}
</View> </View>
); );
BackgroundContainer.propTypes = {
text: PropTypes.string,
theme: PropTypes.string,
loading: PropTypes.bool
};
export default withTheme(BackgroundContainer); export default withTheme(BackgroundContainer);

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text } from 'react-native'; import { StyleSheet, Text } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
@ -7,44 +6,45 @@ import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import ActivityIndicator from '../ActivityIndicator'; import ActivityIndicator from '../ActivityIndicator';
interface IButtonProps {
title: string;
type: string
onPress(): void;
disabled: boolean;
backgroundColor: string
loading: boolean;
theme: string
color: string
fontSize: any
style: any
testID: string;
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
paddingHorizontal: 14, paddingHorizontal: 14,
justifyContent: 'center', justifyContent: 'center',
height: 48, height: 48,
borderRadius: 2, borderRadius: 2,
marginBottom: 12 marginBottom: 12,
}, },
text: { text: {
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium, ...sharedStyles.textMedium,
...sharedStyles.textAlignCenter ...sharedStyles.textAlignCenter,
}, },
disabled: { disabled: {
opacity: 0.3 opacity: 0.3,
} },
}); });
export default class Button extends React.PureComponent { export default class Button extends React.PureComponent<Partial<IButtonProps>, any> {
static propTypes = {
title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
type: PropTypes.string,
onPress: PropTypes.func,
disabled: PropTypes.bool,
backgroundColor: PropTypes.string,
loading: PropTypes.bool,
theme: PropTypes.string,
color: PropTypes.string,
fontSize: PropTypes.string,
style: PropTypes.any
}
static defaultProps = { static defaultProps = {
title: 'Press me!', title: 'Press me!',
type: 'primary', type: 'primary',
onPress: () => alert('It works!'), onPress: () => alert('It works!'),
disabled: false, disabled: false,
loading: false loading: false,
} }
render() { render() {
@ -53,7 +53,7 @@ export default class Button extends React.PureComponent {
} = this.props; } = this.props;
const isPrimary = type === 'primary'; const isPrimary = type === 'primary';
let textColor = isPrimary ? themes[theme].buttonText : themes[theme].bodyText; let textColor = isPrimary ? themes[theme!].buttonText : themes[theme!].bodyText;
if (color) { if (color) {
textColor = color; textColor = color;
} }
@ -66,9 +66,9 @@ export default class Button extends React.PureComponent {
styles.container, styles.container,
backgroundColor backgroundColor
? { backgroundColor } ? { backgroundColor }
: { backgroundColor: isPrimary ? themes[theme].actionTintColor : themes[theme].backgroundColor }, : { backgroundColor: isPrimary ? themes[theme!].actionTintColor : themes[theme!].backgroundColor },
disabled && styles.disabled, disabled && styles.disabled,
style style,
]} ]}
{...otherProps} {...otherProps}
> >
@ -80,7 +80,7 @@ export default class Button extends React.PureComponent {
style={[ style={[
styles.text, styles.text,
{ color: textColor }, { color: textColor },
fontSize && { fontSize } fontSize && { fontSize },
]} ]}
accessibilityLabel={title} accessibilityLabel={title}
> >

View File

@ -1,23 +0,0 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { CustomIcon } from '../lib/Icons';
import { themes } from '../constants/colors';
const styles = StyleSheet.create({
icon: {
width: 22,
height: 22,
marginHorizontal: 15
}
});
const Check = React.memo(({ theme, style }) => <CustomIcon style={[styles.icon, style]} color={themes[theme].tintColor} size={22} name='check' />);
Check.propTypes = {
style: PropTypes.object,
theme: PropTypes.string
};
export default Check;

21
app/containers/Check.tsx Normal file
View File

@ -0,0 +1,21 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { CustomIcon } from '../lib/Icons';
import { themes } from '../constants/colors';
interface ICheck {
style?: object;
theme: string;
}
const styles = StyleSheet.create({
icon: {
width: 22,
height: 22,
marginHorizontal: 15,
},
});
const Check = React.memo(({ theme, style }: ICheck) => <CustomIcon style={[styles.icon, style]} color={themes[theme].tintColor} size={22} name='check' />);
export default Check;

View File

@ -1,13 +1,14 @@
import React from 'react'; import React from 'react';
import FastImage from '@rocket.chat/react-native-fast-image'; import FastImage from '@rocket.chat/react-native-fast-image';
import PropTypes from 'prop-types';
const CustomEmoji = React.memo(({ baseUrl, emoji, style }) => ( import { ICustomEmoji } from './interfaces';
const CustomEmoji = React.memo(({ baseUrl, emoji, style }: ICustomEmoji) => (
<FastImage <FastImage
style={style} style={style}
source={{ source={{
uri: `${ baseUrl }/emoji-custom/${ encodeURIComponent(emoji.content || emoji.name) }.${ emoji.extension }`, uri: `${ baseUrl }/emoji-custom/${ encodeURIComponent(emoji.content || emoji.name) }.${ emoji.extension }`,
priority: FastImage.priority.high priority: FastImage.priority.high,
}} }}
resizeMode={FastImage.resizeMode.contain} resizeMode={FastImage.resizeMode.contain}
/> />
@ -17,10 +18,4 @@ const CustomEmoji = React.memo(({ baseUrl, emoji, style }) => (
return prevEmoji === nextEmoji; return prevEmoji === nextEmoji;
}); });
CustomEmoji.propTypes = {
baseUrl: PropTypes.string.isRequired,
emoji: PropTypes.object.isRequired,
style: PropTypes.any
};
export default CustomEmoji; export default CustomEmoji;

View File

@ -1,15 +1,15 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { FlatList, Text, TouchableOpacity } from 'react-native';
import { Text, TouchableOpacity, FlatList } from 'react-native';
import shortnameToUnicode from '../../utils/shortnameToUnicode'; import shortnameToUnicode from '../../utils/shortnameToUnicode';
import styles from './styles'; import styles from './styles';
import CustomEmoji from './CustomEmoji'; import CustomEmoji from './CustomEmoji';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { IEmoji, IEmojiCategory } from './interfaces';
const EMOJI_SIZE = 50; const EMOJI_SIZE = 50;
const renderEmoji = (emoji, size, baseUrl) => { const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
if (emoji && emoji.isCustom) { if (emoji && emoji.isCustom) {
return <CustomEmoji style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]} emoji={emoji} baseUrl={baseUrl} />; return <CustomEmoji style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]} emoji={emoji} baseUrl={baseUrl} />;
} }
@ -20,25 +20,17 @@ const renderEmoji = (emoji, size, baseUrl) => {
); );
}; };
class EmojiCategory extends React.Component { class EmojiCategory extends React.Component<Partial<IEmojiCategory>> {
static propTypes = { renderItem(emoji: any) {
baseUrl: PropTypes.string.isRequired,
emojis: PropTypes.any,
onEmojiSelected: PropTypes.func,
emojisPerRow: PropTypes.number,
width: PropTypes.number
}
renderItem(emoji) {
const { baseUrl, onEmojiSelected } = this.props; const { baseUrl, onEmojiSelected } = this.props;
return ( return (
<TouchableOpacity <TouchableOpacity
activeOpacity={0.7} activeOpacity={0.7}
key={emoji && emoji.isCustom ? emoji.content : emoji} key={emoji && emoji.isCustom ? emoji.content : emoji}
onPress={() => onEmojiSelected(emoji)} onPress={() => onEmojiSelected!(emoji)}
testID={`reaction-picker-${ emoji && emoji.isCustom ? emoji.content : emoji }`} testID={`reaction-picker-${ emoji && emoji.isCustom ? emoji.content : emoji }`}
> >
{renderEmoji(emoji, EMOJI_SIZE, baseUrl)} {renderEmoji(emoji, EMOJI_SIZE, baseUrl!)}
</TouchableOpacity> </TouchableOpacity>
); );
} }
@ -54,11 +46,12 @@ class EmojiCategory extends React.Component {
const marginHorizontal = (width - (numColumns * EMOJI_SIZE)) / 2; const marginHorizontal = (width - (numColumns * EMOJI_SIZE)) / 2;
return ( return (
// @ts-ignore
<FlatList <FlatList
contentContainerStyle={{ marginHorizontal }} contentContainerStyle={{ marginHorizontal }}
// rerender FlatList in case of width changes // rerender FlatList in case of width changes
key={`emoji-category-${ width }`} key={`emoji-category-${ width }`}
keyExtractor={item => (item && item.isCustom && item.content) || item} keyExtractor={(item) => (item && item.isCustom && item.content) || item}
data={emojis} data={emojis}
extraData={this.props} extraData={this.props}
renderItem={({ item }) => this.renderItem(item)} renderItem={({ item }) => this.renderItem(item)}

View File

@ -1,19 +1,19 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { Text, TouchableOpacity, View } from 'react-native';
import { View, TouchableOpacity, Text } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
export default class TabBar extends React.Component { interface ITabBarProps {
static propTypes = { goToPage: Function;
goToPage: PropTypes.func, activeTab: number,
activeTab: PropTypes.number, tabs: [],
tabs: PropTypes.array, tabEmojiStyle: object,
tabEmojiStyle: PropTypes.object, theme: string
theme: PropTypes.string }
}
shouldComponentUpdate(nextProps) { export default class TabBar extends React.Component<Partial<ITabBarProps>> {
shouldComponentUpdate(nextProps: any) {
const { activeTab, theme } = this.props; const { activeTab, theme } = this.props;
if (nextProps.activeTab !== activeTab) { if (nextProps.activeTab !== activeTab) {
return true; return true;
@ -25,22 +25,20 @@ export default class TabBar extends React.Component {
} }
render() { render() {
const { const { tabs, goToPage, tabEmojiStyle, activeTab, theme } = this.props;
tabs, goToPage, tabEmojiStyle, activeTab, theme
} = this.props;
return ( return (
<View style={styles.tabsContainer}> <View style={styles.tabsContainer}>
{tabs.map((tab, i) => ( {tabs!.map((tab, i) => (
<TouchableOpacity <TouchableOpacity
activeOpacity={0.7} activeOpacity={0.7}
key={tab} key={tab}
onPress={() => goToPage(i)} onPress={() => goToPage!(i)}
style={styles.tab} style={styles.tab}
testID={`reaction-picker-${ tab }`} testID={`reaction-picker-${ tab }`}
> >
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text> <Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
{activeTab === i ? <View style={[styles.activeTabLine, { backgroundColor: themes[theme].tintColor }]} /> : <View style={styles.tabLine} />} {activeTab === i ? <View style={[styles.activeTabLine, { backgroundColor: themes[theme!].tintColor }]} /> : <View style={styles.tabLine} />}
</TouchableOpacity> </TouchableOpacity>
))} ))}
</View> </View>

View File

@ -2,43 +2,43 @@ const list = ['frequentlyUsed', 'custom', 'people', 'nature', 'food', 'activity'
const tabs = [ const tabs = [
{ {
tabLabel: '🕒', tabLabel: '🕒',
category: list[0] category: list[0],
}, },
{ {
tabLabel: '🚀', tabLabel: '🚀',
category: list[1] category: list[1],
}, },
{ {
tabLabel: '😃', tabLabel: '😃',
category: list[2] category: list[2],
}, },
{ {
tabLabel: '🐶', tabLabel: '🐶',
category: list[3] category: list[3],
}, },
{ {
tabLabel: '🍔', tabLabel: '🍔',
category: list[4] category: list[4],
}, },
{ {
tabLabel: '⚽', tabLabel: '⚽',
category: list[5] category: list[5],
}, },
{ {
tabLabel: '🚌', tabLabel: '🚌',
category: list[6] category: list[6],
}, },
{ {
tabLabel: '💡', tabLabel: '💡',
category: list[7] category: list[7],
}, },
{ {
tabLabel: '💛', tabLabel: '💛',
category: list[8] category: list[8],
}, },
{ {
tabLabel: '🏁', tabLabel: '🏁',
category: list[9] category: list[9],
} },
]; ];
export default { list, tabs }; export default { list, tabs };

View File

@ -1,6 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import PropTypes from 'prop-types';
import ScrollableTabView from 'react-native-scrollable-tab-view'; import ScrollableTabView from 'react-native-scrollable-tab-view';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -18,35 +17,46 @@ import shortnameToUnicode from '../../utils/shortnameToUnicode';
import log from '../../utils/log'; import log from '../../utils/log';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { IEmoji } from './interfaces';
const scrollProps = { const scrollProps = {
keyboardShouldPersistTaps: 'always', keyboardShouldPersistTaps: 'always',
keyboardDismissMode: 'none' keyboardDismissMode: 'none',
}; };
class EmojiPicker extends Component { interface IEmojiPickerProps {
static propTypes = { isMessageContainsOnlyEmoji: boolean;
baseUrl: PropTypes.string.isRequired, getCustomEmoji?: Function;
customEmojis: PropTypes.object, baseUrl: string;
onEmojiSelected: PropTypes.func, customEmojis?: any;
tabEmojiStyle: PropTypes.object, style: object;
theme: PropTypes.string theme?: string;
}; onEmojiSelected?: Function;
tabEmojiStyle?: object;
}
constructor(props) { interface IEmojiPickerState {
frequentlyUsed: [];
customEmojis: any;
show: boolean;
width: number | null;
}
class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
constructor(props: IEmojiPickerProps) {
super(props); super(props);
const customEmojis = Object.keys(props.customEmojis) const customEmojis = Object.keys(props.customEmojis)
.filter(item => item === props.customEmojis[item].name) .filter((item) => item === props.customEmojis[item].name)
.map(item => ({ .map((item) => ({
content: props.customEmojis[item].name, content: props.customEmojis[item].name,
extension: props.customEmojis[item].extension, extension: props.customEmojis[item].extension,
isCustom: true isCustom: true,
})); }));
this.state = { this.state = {
frequentlyUsed: [], frequentlyUsed: [],
customEmojis, customEmojis,
show: false, show: false,
width: null width: null,
}; };
} }
@ -55,7 +65,7 @@ class EmojiPicker extends Component {
this.setState({ show: true }); this.setState({ show: true });
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: any, nextState: any) {
const { frequentlyUsed, show, width } = this.state; const { frequentlyUsed, show, width } = this.state;
const { theme } = this.props; const { theme } = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
@ -73,19 +83,19 @@ class EmojiPicker extends Component {
return false; return false;
} }
onEmojiSelected = (emoji) => { onEmojiSelected = (emoji: IEmoji) => {
try { try {
const { onEmojiSelected } = this.props; const { onEmojiSelected } = this.props;
if (emoji.isCustom) { if (emoji.isCustom) {
this._addFrequentlyUsed({ this._addFrequentlyUsed({
content: emoji.content, extension: emoji.extension, isCustom: true content: emoji.content, extension: emoji.extension, isCustom: true,
}); });
onEmojiSelected(`:${ emoji.content }:`); onEmojiSelected!(`:${ emoji.content }:`);
} else { } else {
const content = emoji; const content = emoji;
this._addFrequentlyUsed({ content, isCustom: false }); this._addFrequentlyUsed({ content, isCustom: false });
const shortname = `:${ emoji }:`; const shortname = `:${ emoji }:`;
onEmojiSelected(shortnameToUnicode(shortname), shortname); onEmojiSelected!(shortnameToUnicode(shortname), shortname);
} }
} catch (e) { } catch (e) {
log(e); log(e);
@ -93,23 +103,23 @@ class EmojiPicker extends Component {
} }
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
_addFrequentlyUsed = protectedFunction(async(emoji) => { _addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => {
const db = database.active; const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis'); const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojiRecord; let freqEmojiRecord: any;
try { try {
freqEmojiRecord = await freqEmojiCollection.find(emoji.content); freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
} catch (error) { } catch (error) {
// Do nothing // Do nothing
} }
await db.action(async() => { await db.action(async () => {
if (freqEmojiRecord) { if (freqEmojiRecord) {
await freqEmojiRecord.update((f) => { await freqEmojiRecord.update((f: any) => {
f.count += 1; f.count += 1;
}); });
} else { } else {
await freqEmojiCollection.create((f) => { await freqEmojiCollection.create((f: any) => {
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema); f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
Object.assign(f, emoji); Object.assign(f, emoji);
f.count = 1; f.count = 1;
@ -118,11 +128,11 @@ class EmojiPicker extends Component {
}); });
}) })
updateFrequentlyUsed = async() => { updateFrequentlyUsed = async () => {
const db = database.active; const db = database.active;
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch(); const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
let frequentlyUsed = orderBy(frequentlyUsedRecords, ['count'], ['desc']); let frequentlyUsed: any = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
frequentlyUsed = frequentlyUsed.map((item) => { frequentlyUsed = frequentlyUsed.map((item: IEmoji) => {
if (item.isCustom) { if (item.isCustom) {
return { content: item.content, extension: item.extension, isCustom: item.isCustom }; return { content: item.content, extension: item.extension, isCustom: item.isCustom };
} }
@ -131,9 +141,9 @@ class EmojiPicker extends Component {
this.setState({ frequentlyUsed }); this.setState({ frequentlyUsed });
} }
onLayout = ({ nativeEvent: { layout: { width } } }) => this.setState({ width }); onLayout = ({ nativeEvent: { layout: { width } } }: any) => this.setState({ width });
renderCategory(category, i, label) { renderCategory(category: any, i: number, label: string) {
const { frequentlyUsed, customEmojis, width } = this.state; const { frequentlyUsed, customEmojis, width } = this.state;
const { baseUrl } = this.props; const { baseUrl } = this.props;
@ -148,9 +158,9 @@ class EmojiPicker extends Component {
return ( return (
<EmojiCategory <EmojiCategory
emojis={emojis} emojis={emojis}
onEmojiSelected={emoji => this.onEmojiSelected(emoji)} onEmojiSelected={(emoji) => this.onEmojiSelected(emoji)}
style={styles.categoryContainer} style={styles.categoryContainer}
width={width} width={width!}
baseUrl={baseUrl} baseUrl={baseUrl}
tabLabel={label} tabLabel={label}
/> />
@ -168,15 +178,15 @@ class EmojiPicker extends Component {
<View onLayout={this.onLayout} style={{ flex: 1 }}> <View onLayout={this.onLayout} style={{ flex: 1 }}>
<ScrollableTabView <ScrollableTabView
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />} renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
/* @ts-ignore*/
contentProps={scrollProps} contentProps={scrollProps}
style={{ backgroundColor: themes[theme].focusedBackground }} style={{ backgroundColor: themes[theme!].focusedBackground }}
> >
{ {
categories.tabs.map((tab, i) => ( categories.tabs.map((tab, i) => (
(i === 0 && frequentlyUsed.length === 0) ? null // when no frequentlyUsed don't show the tab i === 0 && frequentlyUsed.length === 0 ? null // when no frequentlyUsed don't show the tab
: ( : this.renderCategory(tab.category, i, tab.tabLabel)
this.renderCategory(tab.category, i, tab.tabLabel) ))
)))
} }
</ScrollableTabView> </ScrollableTabView>
</View> </View>
@ -184,8 +194,8 @@ class EmojiPicker extends Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
customEmojis: state.customEmojis customEmojis: state.customEmojis,
}); });
export default connect(mapStateToProps)(withTheme(EmojiPicker)); export default connect(mapStateToProps)(withTheme(EmojiPicker));

View File

@ -0,0 +1,22 @@
export interface IEmoji {
content: any;
name: string;
extension: any;
isCustom: boolean;
}
export interface ICustomEmoji {
baseUrl: string,
emoji: IEmoji,
style: any
}
export interface IEmojiCategory {
baseUrl: string;
emojis: IEmoji[];
onEmojiSelected: Function;
emojisPerRow: number;
width: number;
style: any;
tabLabel: string;
}

View File

@ -4,29 +4,29 @@ import sharedStyles from '../../views/Styles';
export default StyleSheet.create({ export default StyleSheet.create({
container: { container: {
flex: 1 flex: 1,
}, },
tabsContainer: { tabsContainer: {
height: 45, height: 45,
flexDirection: 'row', flexDirection: 'row',
paddingTop: 5 paddingTop: 5,
}, },
tab: { tab: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingBottom: 10 paddingBottom: 10,
}, },
tabEmoji: { tabEmoji: {
fontSize: 20, fontSize: 20,
color: 'black' color: 'black',
}, },
activeTabLine: { activeTabLine: {
position: 'absolute', position: 'absolute',
left: 0, left: 0,
right: 0, right: 0,
height: 2, height: 2,
bottom: 0 bottom: 0,
}, },
tabLine: { tabLine: {
position: 'absolute', position: 'absolute',
@ -34,25 +34,25 @@ export default StyleSheet.create({
right: 0, right: 0,
height: 2, height: 2,
backgroundColor: 'rgba(0,0,0,0.05)', backgroundColor: 'rgba(0,0,0,0.05)',
bottom: 0 bottom: 0,
}, },
categoryContainer: { categoryContainer: {
flex: 1, flex: 1,
alignItems: 'flex-start' alignItems: 'flex-start',
}, },
categoryInner: { categoryInner: {
flexWrap: 'wrap', flexWrap: 'wrap',
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-start', justifyContent: 'flex-start',
flex: 1 flex: 1,
}, },
categoryEmoji: { categoryEmoji: {
...sharedStyles.textAlignCenter, ...sharedStyles.textAlignCenter,
backgroundColor: 'transparent', backgroundColor: 'transparent',
color: '#ffffff' color: '#ffffff',
}, },
customCategoryEmoji: { customCategoryEmoji: {
margin: 8 margin: 8,
} },
}); });

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { ScrollView, StyleSheet, View } from 'react-native'; import { ScrollView, StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
@ -11,27 +10,33 @@ import AppVersion from './AppVersion';
import { isTablet } from '../utils/deviceInfo'; import { isTablet } from '../utils/deviceInfo';
import SafeAreaView from './SafeAreaView'; import SafeAreaView from './SafeAreaView';
interface IFormContainer {
theme: string;
testID: string;
children: JSX.Element;
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
scrollView: { scrollView: {
minHeight: '100%' minHeight: '100%',
} },
}); });
export const FormContainerInner = ({ children }) => ( export const FormContainerInner = ({ children }: {children: JSX.Element}) => (
<View style={[sharedStyles.container, isTablet && sharedStyles.tabletScreenContent]}> <View style={[sharedStyles.container, isTablet && sharedStyles.tabletScreenContent]}>
{children} {children}
</View> </View>
); );
const FormContainer = ({ const FormContainer = ({ children, theme, testID, ...props }: IFormContainer) => (
children, theme, testID, ...props // @ts-ignore
}) => (
<KeyboardView <KeyboardView
style={{ backgroundColor: themes[theme].backgroundColor }} style={{ backgroundColor: themes[theme].backgroundColor }}
contentContainerStyle={sharedStyles.container} contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<StatusBar /> <StatusBar />
{/* @ts-ignore*/}
<ScrollView <ScrollView
style={sharedStyles.container} style={sharedStyles.container}
contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}
@ -46,14 +51,4 @@ const FormContainer = ({
</KeyboardView> </KeyboardView>
); );
FormContainer.propTypes = {
theme: PropTypes.string,
testID: PropTypes.string,
children: PropTypes.element
};
FormContainerInner.propTypes = {
children: PropTypes.element
};
export default FormContainer; export default FormContainer;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { View, StyleSheet } from 'react-native'; import { StyleSheet, View } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { themedHeader } from '../../utils/navigation'; import { themedHeader } from '../../utils/navigation';
import { isIOS, isTablet } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
@ -10,20 +10,27 @@ import { withTheme } from '../../theme';
// Get from https://github.com/react-navigation/react-navigation/blob/master/packages/stack/src/views/Header/HeaderSegment.tsx#L69 // Get from https://github.com/react-navigation/react-navigation/blob/master/packages/stack/src/views/Header/HeaderSegment.tsx#L69
export const headerHeight = isIOS ? 44 : 56; export const headerHeight = isIOS ? 44 : 56;
export const getHeaderHeight = (isLandscape) => { export const getHeaderHeight = (isLandscape: boolean) => {
if (isIOS) { if (isIOS) {
if (isLandscape && !isTablet) { if (isLandscape && !isTablet) {
return 32; return 32;
} else {
return 44;
} }
return 44;
} }
return 56; return 56;
}; };
export const getHeaderTitlePosition = ({ insets, numIconsRight }) => ({ interface IHeaderTitlePosition {
insets: {
left: number;
right: number;
};
numIconsRight: number;
}
export const getHeaderTitlePosition = ({ insets, numIconsRight }: IHeaderTitlePosition) => ({
left: insets.left + 60, left: insets.left + 60,
right: insets.right + Math.max(45 * numIconsRight, 15) right: insets.right + Math.max(45 * numIconsRight, 15),
}); });
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -31,13 +38,18 @@ const styles = StyleSheet.create({
height: headerHeight, height: headerHeight,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
elevation: 4 elevation: 4,
} },
}); });
const Header = ({ interface IHeader {
theme, headerLeft, headerTitle, headerRight theme: string;
}) => ( headerLeft(): void;
headerTitle(): void;
headerRight(): void;
}
const Header = ({ theme, headerLeft, headerTitle, headerRight }: IHeader) => (
<SafeAreaView style={{ backgroundColor: themes[theme].headerBackground }} edges={['top', 'left', 'right']}> <SafeAreaView style={{ backgroundColor: themes[theme].headerBackground }} edges={['top', 'left', 'right']}>
<View style={[styles.container, { ...themedHeader(theme).headerStyle }]}> <View style={[styles.container, { ...themedHeader(theme).headerStyle }]}>
{headerLeft ? headerLeft() : null} {headerLeft ? headerLeft() : null}
@ -47,11 +59,4 @@ const Header = ({
</SafeAreaView> </SafeAreaView>
); );
Header.propTypes = {
theme: PropTypes.string,
headerLeft: PropTypes.element,
headerTitle: PropTypes.element,
headerRight: PropTypes.element
};
export default withTheme(Header); export default withTheme(Header);

View File

@ -1,27 +1,30 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { isIOS } from '../../utils/deviceInfo'; import { isIOS } from '../../utils/deviceInfo';
import I18n from '../../i18n'; import I18n from '../../i18n';
import Container from './HeaderButtonContainer'; import Container from './HeaderButtonContainer';
import Item from './HeaderButtonItem'; import Item from './HeaderButtonItem';
interface IHeaderButtonCommon {
navigation: any;
onPress(): void;
testID: string;
}
// Left // Left
export const Drawer = React.memo(({ navigation, testID, ...props }) => ( export const Drawer = React.memo(({ navigation, testID, ...props }: Partial<IHeaderButtonCommon>) => (
<Container left> <Container left>
<Item iconName='hamburguer' onPress={() => navigation.toggleDrawer()} testID={testID} {...props} /> <Item iconName='hamburguer' onPress={() => navigation.toggleDrawer()} testID={testID} {...props} />
</Container> </Container>
)); ));
export const CloseModal = React.memo(({ export const CloseModal = React.memo(({ navigation, testID, onPress = () => navigation.pop(), ...props }: IHeaderButtonCommon) => (
navigation, testID, onPress = () => navigation.pop(), ...props
}) => (
<Container left> <Container left>
<Item iconName='close' onPress={onPress} testID={testID} {...props} /> <Item iconName='close' onPress={onPress} testID={testID} {...props} />
</Container> </Container>
)); ));
export const CancelModal = React.memo(({ onPress, testID }) => ( export const CancelModal = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => (
<Container left> <Container left>
{isIOS {isIOS
? <Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} /> ? <Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} />
@ -31,54 +34,24 @@ export const CancelModal = React.memo(({ onPress, testID }) => (
)); ));
// Right // Right
export const More = React.memo(({ onPress, testID }) => ( export const More = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => (
<Container> <Container>
<Item iconName='kebab' onPress={onPress} testID={testID} /> <Item iconName='kebab' onPress={onPress} testID={testID} />
</Container> </Container>
)); ));
export const Download = React.memo(({ onPress, testID, ...props }) => ( export const Download = React.memo(({ onPress, testID, ...props }: Partial<IHeaderButtonCommon>) => (
<Container> <Container>
<Item iconName='download' onPress={onPress} testID={testID} {...props} /> <Item iconName='download' onPress={onPress} testID={testID} {...props} />
</Container> </Container>
)); ));
export const Preferences = React.memo(({ onPress, testID, ...props }) => ( export const Preferences = React.memo(({ onPress, testID, ...props }: Partial<IHeaderButtonCommon>) => (
<Container> <Container>
<Item iconName='settings' onPress={onPress} testID={testID} {...props} /> <Item iconName='settings' onPress={onPress} testID={testID} {...props} />
</Container> </Container>
)); ));
export const Legal = React.memo(({ navigation, testID }) => ( export const Legal = React.memo(({ navigation, testID }: Partial<IHeaderButtonCommon>) => (
<More onPress={() => navigation.navigate('LegalView')} testID={testID} /> <More onPress={() => navigation.navigate('LegalView')} testID={testID} />
)); ));
Drawer.propTypes = {
navigation: PropTypes.object.isRequired,
testID: PropTypes.string.isRequired
};
CloseModal.propTypes = {
navigation: PropTypes.object.isRequired,
testID: PropTypes.string.isRequired,
onPress: PropTypes.func
};
CancelModal.propTypes = {
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired
};
More.propTypes = {
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired
};
Download.propTypes = {
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired
};
Preferences.propTypes = {
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired
};
Legal.propTypes = {
navigation: PropTypes.object.isRequired,
testID: PropTypes.string.isRequired
};

View File

@ -1,36 +0,0 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
},
left: {
marginLeft: 5
},
right: {
marginRight: 5
}
});
const Container = ({ children, left }) => (
<View style={[styles.container, left ? styles.left : styles.right]}>
{children}
</View>
);
Container.propTypes = {
children: PropTypes.arrayOf(PropTypes.element),
left: PropTypes.bool
};
Container.defaultProps = {
left: false
};
Container.displayName = 'HeaderButton.Container';
export default Container;

View File

@ -0,0 +1,31 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
interface IHeaderButtonContainer {
children: JSX.Element;
left?: boolean;
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
left: {
marginLeft: 5,
},
right: {
marginRight: 5,
},
});
const Container = ({ children, left = false }: IHeaderButtonContainer) => (
<View style={[styles.container, left ? styles.left : styles.right]}>
{children}
</View>
);
Container.displayName = 'HeaderButton.Container';
export default Container;

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { Text, StyleSheet, Platform } from 'react-native'; import { Platform, StyleSheet, Text } from 'react-native';
import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
@ -8,30 +7,37 @@ import { withTheme } from '../../theme';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
interface IHeaderButtonItem {
title: string;
iconName: string;
onPress(): void;
testID: string;
theme: string;
badge(): void;
}
export const BUTTON_HIT_SLOP = { export const BUTTON_HIT_SLOP = {
top: 5, right: 5, bottom: 5, left: 5 top: 5, right: 5, bottom: 5, left: 5,
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
marginHorizontal: 6 marginHorizontal: 6,
}, },
title: { title: {
...Platform.select({ ...Platform.select({
android: { android: {
fontSize: 14 fontSize: 14,
}, },
default: { default: {
fontSize: 17 fontSize: 17,
} },
}), }),
...sharedStyles.textRegular ...sharedStyles.textRegular,
} },
}); });
const Item = ({ const Item = ({ title, iconName, onPress, testID, theme, badge }: IHeaderButtonItem) => (
title, iconName, onPress, testID, theme, badge
}) => (
<Touchable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} style={styles.container}> <Touchable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} style={styles.container}>
<> <>
{ {
@ -44,15 +50,6 @@ const Item = ({
</Touchable> </Touchable>
); );
Item.propTypes = {
onPress: PropTypes.func.isRequired,
title: PropTypes.string,
iconName: PropTypes.string,
testID: PropTypes.string,
theme: PropTypes.string,
badge: PropTypes.func
};
Item.displayName = 'HeaderButton.Item'; Item.displayName = 'HeaderButton.Item';
export default withTheme(Item); export default withTheme(Item);

View File

@ -11,8 +11,8 @@ const styles = StyleSheet.create({
top: -3, top: -3,
borderRadius: 10, borderRadius: 10,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
} },
}); });
export const Badge = ({ ...props }) => ( export const Badge = ({ ...props }) => (

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View, Text } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Notifier } from 'react-native-notifier'; import { Notifier } from 'react-native-notifier';
@ -16,10 +15,13 @@ import { goRoom } from '../../utils/goRoom';
import Navigation from '../../lib/Navigation'; import Navigation from '../../lib/Navigation';
import { useOrientation } from '../../dimensions'; import { useOrientation } from '../../dimensions';
interface INotifierComponent {
notification: object;
isMasterDetail: boolean;
}
const AVATAR_SIZE = 48; const AVATAR_SIZE = 48;
const BUTTON_HIT_SLOP = { const BUTTON_HIT_SLOP = { top: 12, right: 12, bottom: 12, left: 12 };
top: 12, right: 12, bottom: 12, left: 12
};
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -30,50 +32,50 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
marginHorizontal: 10, marginHorizontal: 10,
borderWidth: StyleSheet.hairlineWidth, borderWidth: StyleSheet.hairlineWidth,
borderRadius: 4 borderRadius: 4,
}, },
content: { content: {
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'center',
}, },
inner: { inner: {
flex: 1 flex: 1,
}, },
avatar: { avatar: {
marginRight: 10 marginRight: 10,
}, },
roomName: { roomName: {
fontSize: 17, fontSize: 17,
lineHeight: 20, lineHeight: 20,
...sharedStyles.textMedium ...sharedStyles.textMedium,
}, },
message: { message: {
fontSize: 14, fontSize: 14,
lineHeight: 17, lineHeight: 17,
...sharedStyles.textRegular ...sharedStyles.textRegular,
}, },
close: { close: {
marginLeft: 10 marginLeft: 10,
}, },
small: { small: {
width: '50%', width: '50%',
alignSelf: 'center' alignSelf: 'center',
} },
}); });
const hideNotification = () => Notifier.hideNotification(); const hideNotification = () => Notifier.hideNotification();
const NotifierComponent = React.memo(({ notification, isMasterDetail }) => { const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifierComponent) => {
const { theme } = useTheme(); const { theme }: any = useTheme();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const { isLandscape } = useOrientation(); const { isLandscape } = useOrientation();
const { text, payload } = notification; const { text, payload }: any = notification;
const { type, rid } = payload; const { type, rid } = payload;
const name = type === 'd' ? payload.sender.username : payload.name; const name = type === 'd' ? payload.sender.username : payload.name;
// if sub is not on local database, title and avatar will be null, so we use payload from notification // if sub is not on local database, title and avatar will be null, so we use payload from notification
const { title = name, avatar = name } = notification; const { title = name, avatar = name }: any = notification;
const onPress = () => { const onPress = () => {
const { prid, _id } = payload; const { prid, _id } = payload;
@ -81,7 +83,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => {
return; return;
} }
const item = { const item = {
rid, name: title, t: type, prid rid, name: title, t: type, prid,
}; };
if (isMasterDetail) { if (isMasterDetail) {
@ -100,8 +102,8 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => {
{ {
backgroundColor: themes[theme].focusedBackground, backgroundColor: themes[theme].focusedBackground,
borderColor: themes[theme].separatorColor, borderColor: themes[theme].separatorColor,
marginTop: insets.top marginTop: insets.top,
} },
]} ]}
> >
<Touchable <Touchable
@ -129,13 +131,8 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => {
); );
}); });
NotifierComponent.propTypes = { const mapStateToProps = (state: any) => ({
notification: PropTypes.object, isMasterDetail: state.app.isMasterDetail,
isMasterDetail: PropTypes.bool
};
const mapStateToProps = state => ({
isMasterDetail: state.app.isMasterDetail
}); });
export default connect(mapStateToProps)(NotifierComponent); export default connect(mapStateToProps)(NotifierComponent);

View File

@ -1,6 +1,5 @@
import React, { memo, useEffect } from 'react'; import React, { memo, useEffect } from 'react';
import PropTypes from 'prop-types'; import { Easing, Notifier, NotifierRoot } from 'react-native-notifier';
import { NotifierRoot, Notifier, Easing } from 'react-native-notifier';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
@ -11,8 +10,8 @@ import { getActiveRoute } from '../../utils/navigation';
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp'; export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
const InAppNotification = memo(({ rooms, appState }) => { const InAppNotification = memo(({ rooms, appState }: {rooms: any, appState: string}) => {
const show = (notification) => { const show = (notification: any) => {
if (appState !== 'foreground') { if (appState !== 'foreground') {
return; return;
} }
@ -28,8 +27,8 @@ const InAppNotification = memo(({ rooms, appState }) => {
showEasing: Easing.inOut(Easing.quad), showEasing: Easing.inOut(Easing.quad),
Component: NotifierComponent, Component: NotifierComponent,
componentProps: { componentProps: {
notification notification,
} },
}); });
} }
}; };
@ -44,14 +43,9 @@ const InAppNotification = memo(({ rooms, appState }) => {
return <NotifierRoot />; return <NotifierRoot />;
}, (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms)); }, (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms));
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
rooms: state.room.rooms, rooms: state.room.rooms,
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background' appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
}); });
InAppNotification.propTypes = {
rooms: PropTypes.array,
appState: PropTypes.string
};
export default connect(mapStateToProps)(InAppNotification); export default connect(mapStateToProps)(InAppNotification);

View File

@ -1,16 +1,20 @@
import React from 'react'; import React from 'react';
import { ScrollView, StyleSheet } from 'react-native'; import { ScrollView, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import scrollPersistTaps from '../../utils/scrollPersistTaps';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
paddingVertical: 16 paddingVertical: 16,
} },
}); });
const ListContainer = React.memo(({ children, ...props }) => ( interface IListContainer {
children: JSX.Element;
}
const ListContainer = React.memo(({ children, ...props }: IListContainer) => (
// @ts-ignore
<ScrollView <ScrollView
contentContainerStyle={styles.container} contentContainerStyle={styles.container}
scrollIndicatorInsets={{ right: 1 }} // https://github.com/facebook/react-native/issues/26610#issuecomment-539843444 scrollIndicatorInsets={{ right: 1 }} // https://github.com/facebook/react-native/issues/26610#issuecomment-539843444
@ -21,10 +25,6 @@ const ListContainer = React.memo(({ children, ...props }) => (
</ScrollView> </ScrollView>
)); ));
ListContainer.propTypes = {
children: PropTypes.array.isRequired
};
ListContainer.displayName = 'List.Container'; ListContainer.displayName = 'List.Container';
export default withTheme(ListContainer); export default withTheme(ListContainer);

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -11,30 +10,26 @@ import { PADDING_HORIZONTAL } from './constants';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
paddingBottom: 12, paddingBottom: 12,
paddingHorizontal: PADDING_HORIZONTAL paddingHorizontal: PADDING_HORIZONTAL,
}, },
title: { title: {
fontSize: 16, fontSize: 16,
...sharedStyles.textRegular ...sharedStyles.textRegular,
} },
}); });
const ListHeader = React.memo(({ title, theme, translateTitle }) => ( interface IListHeader {
title: string;
theme: string;
translateTitle: boolean;
}
const ListHeader = React.memo(({ title, theme, translateTitle = true }: IListHeader) => (
<View style={styles.container}> <View style={styles.container}>
<Text style={[styles.title, { color: themes[theme].infoText }]} numberOfLines={1}>{translateTitle ? I18n.t(title) : title}</Text> <Text style={[styles.title, { color: themes[theme].infoText }]} numberOfLines={1}>{translateTitle ? I18n.t(title) : title}</Text>
</View> </View>
)); ));
ListHeader.propTypes = {
title: PropTypes.string,
theme: PropTypes.string,
translateTitle: PropTypes.bool
};
ListHeader.defaultProps = {
translateTitle: true
};
ListHeader.displayName = 'List.Header'; ListHeader.displayName = 'List.Header';
export default withTheme(ListHeader); export default withTheme(ListHeader);

View File

@ -1,26 +1,27 @@
import React from 'react'; import React from 'react';
import { View, StyleSheet } from 'react-native'; import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { ICON_SIZE } from './constants'; import { ICON_SIZE } from './constants';
interface IListIcon {
theme: string;
name: string;
color: string;
style: object;
testID: string;
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
icon: { icon: {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
} },
}); });
const ListIcon = React.memo(({ const ListIcon = React.memo(({ theme, name, color, style, testID }: IListIcon) => (
theme,
name,
color,
style,
testID
}) => (
<View style={[styles.icon, style]}> <View style={[styles.icon, style]}>
<CustomIcon <CustomIcon
name={name} name={name}
@ -31,14 +32,6 @@ const ListIcon = React.memo(({
</View> </View>
)); ));
ListIcon.propTypes = {
theme: PropTypes.string,
name: PropTypes.string,
color: PropTypes.string,
style: PropTypes.object,
testID: PropTypes.string
};
ListIcon.displayName = 'List.Icon'; ListIcon.displayName = 'List.Icon';
export default withTheme(ListIcon); export default withTheme(ListIcon);

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -11,30 +10,26 @@ import I18n from '../../i18n';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
paddingTop: 8, paddingTop: 8,
paddingHorizontal: PADDING_HORIZONTAL paddingHorizontal: PADDING_HORIZONTAL,
}, },
text: { text: {
fontSize: 14, fontSize: 14,
...sharedStyles.textRegular ...sharedStyles.textRegular,
} },
}); });
const ListInfo = React.memo(({ info, translateInfo, theme }) => ( interface IListHeader {
info: string;
theme: string;
translateInfo: boolean;
}
const ListInfo = React.memo(({ info, theme, translateInfo = true }: IListHeader) => (
<View style={styles.container}> <View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].infoText }]}>{translateInfo ? I18n.t(info) : info}</Text> <Text style={[styles.text, { color: themes[theme].infoText }]}>{translateInfo ? I18n.t(info) : info}</Text>
</View> </View>
)); ));
ListInfo.propTypes = {
info: PropTypes.string,
theme: PropTypes.string,
translateInfo: PropTypes.bool
};
ListInfo.defaultProps = {
translateInfo: true
};
ListInfo.displayName = 'List.Info'; ListInfo.displayName = 'List.Info';
export default withTheme(ListInfo); export default withTheme(ListInfo);

View File

@ -1,8 +1,5 @@
import React from 'react'; import React from 'react';
import { import { I18nManager, StyleSheet, Text, View } from 'react-native';
View, Text, StyleSheet, I18nManager
} from 'react-native';
import PropTypes from 'prop-types';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -20,48 +17,64 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingHorizontal: PADDING_HORIZONTAL paddingHorizontal: PADDING_HORIZONTAL,
}, },
leftContainer: { leftContainer: {
paddingRight: PADDING_HORIZONTAL paddingRight: PADDING_HORIZONTAL,
}, },
rightContainer: { rightContainer: {
paddingLeft: PADDING_HORIZONTAL paddingLeft: PADDING_HORIZONTAL,
}, },
disabled: { disabled: {
opacity: 0.3 opacity: 0.3,
}, },
textContainer: { textContainer: {
flex: 1, flex: 1,
justifyContent: 'center' justifyContent: 'center',
}, },
textAlertContainer: { textAlertContainer: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'center',
}, },
alertIcon: { alertIcon: {
paddingLeft: 4 paddingLeft: 4,
}, },
title: { title: {
flexShrink: 1, flexShrink: 1,
fontSize: 16, fontSize: 16,
...sharedStyles.textRegular ...sharedStyles.textRegular,
}, },
subtitle: { subtitle: {
fontSize: 14, fontSize: 14,
...sharedStyles.textRegular ...sharedStyles.textRegular,
}, },
actionIndicator: { actionIndicator: {
...I18nManager.isRTL ...I18nManager.isRTL
? { transform: [{ rotate: '180deg' }] } ? { transform: [{ rotate: '180deg' }] }
: {} : {},
} },
}); });
interface IListItemContent {
title?: string;
subtitle?: string;
left?: Function;
right?: Function;
disabled?: boolean;
testID?: string;
theme: string;
color?: string;
translateTitle?: boolean;
translateSubtitle?: boolean;
showActionIndicator?: boolean;
fontScale?: number;
alert?: boolean;
}
const Content = React.memo(({ const Content = React.memo(({
title, subtitle, disabled, testID, left, right, color, theme, translateTitle, translateSubtitle, showActionIndicator, fontScale, alert title, subtitle, disabled, testID, left, right, color, theme, fontScale, alert, translateTitle = true, translateSubtitle = true, showActionIndicator = false,
}) => ( }: IListItemContent) => (
<View style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale }]} testID={testID}> <View style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale! }]} testID={testID}>
{left {left
? ( ? (
<View style={styles.leftContainer}> <View style={styles.leftContainer}>
@ -92,9 +105,16 @@ const Content = React.memo(({
</View> </View>
)); ));
const Button = React.memo(({ interface IListItemButton {
onPress, backgroundColor, underlayColor, ...props title?: string;
}) => ( onPress: Function;
disabled?: boolean;
theme: string;
backgroundColor: string;
underlayColor?: string;
}
const Button = React.memo(({ onPress, backgroundColor, underlayColor, ...props }: IListItemButton) => (
<Touch <Touch
onPress={() => onPress(props.title)} onPress={() => onPress(props.title)}
style={{ backgroundColor: backgroundColor || themes[props.theme].backgroundColor }} style={{ backgroundColor: backgroundColor || themes[props.theme].backgroundColor }}
@ -106,7 +126,13 @@ const Button = React.memo(({
</Touch> </Touch>
)); ));
const ListItem = React.memo(({ ...props }) => { interface IListItem {
onPress: Function;
theme: string;
backgroundColor: string;
}
const ListItem = React.memo(({ ...props }: IListItem) => {
if (props.onPress) { if (props.onPress) {
return <Button {...props} />; return <Button {...props} />;
} }
@ -117,47 +143,6 @@ const ListItem = React.memo(({ ...props }) => {
); );
}); });
ListItem.propTypes = {
onPress: PropTypes.func,
theme: PropTypes.string,
backgroundColor: PropTypes.string
};
ListItem.displayName = 'List.Item'; ListItem.displayName = 'List.Item';
Content.propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string,
left: PropTypes.func,
right: PropTypes.func,
disabled: PropTypes.bool,
testID: PropTypes.string,
theme: PropTypes.string,
color: PropTypes.string,
translateTitle: PropTypes.bool,
translateSubtitle: PropTypes.bool,
showActionIndicator: PropTypes.bool,
fontScale: PropTypes.number,
alert: PropTypes.bool
};
Content.defaultProps = {
translateTitle: true,
translateSubtitle: true,
showActionIndicator: false
};
Button.propTypes = {
title: PropTypes.string,
onPress: PropTypes.func,
disabled: PropTypes.bool,
theme: PropTypes.string,
backgroundColor: PropTypes.string,
underlayColor: PropTypes.string
};
Button.defaultProps = {
disabled: false
};
export default withTheme(withDimensions(ListItem)); export default withTheme(withDimensions(ListItem));

View File

@ -1,28 +1,28 @@
import React from 'react'; import React from 'react';
import { View, StyleSheet } from 'react-native'; import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { Header } from '.'; import { Header } from '.';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
marginVertical: 16 marginVertical: 16,
} },
}); });
const ListSection = React.memo(({ children, title, translateTitle }) => ( interface IListSection {
children: JSX.Element;
title: string;
translateTitle: boolean;
}
const ListSection = React.memo(({ children, title, translateTitle }: IListSection) => (
<View style={styles.container}> <View style={styles.container}>
{title ? <Header {...{ title, translateTitle }} /> : null} {title ? <Header {...{ title, translateTitle }} /> : null}
{children} {children}
</View> </View>
)); ));
ListSection.propTypes = {
children: PropTypes.array.isRequired,
title: PropTypes.string,
translateTitle: PropTypes.bool
};
ListSection.displayName = 'List.Section'; ListSection.displayName = 'List.Section';
export default withTheme(ListSection); export default withTheme(ListSection);

View File

@ -1,32 +1,30 @@
import React from 'react'; import React from 'react';
import { View, StyleSheet } from 'react-native'; import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
separator: { separator: {
height: StyleSheet.hairlineWidth height: StyleSheet.hairlineWidth,
} },
}); });
interface IListSeparator {
style: object;
theme: string;
}
const ListSeparator = React.memo(({ style, theme }) => ( const ListSeparator = React.memo(({ style, theme }: IListSeparator) => (
<View <View
style={[ style={[
styles.separator, styles.separator,
style, style,
{ backgroundColor: themes[theme].separatorColor } { backgroundColor: themes[theme].separatorColor },
]} ]}
/> />
)); ));
ListSeparator.propTypes = {
style: PropTypes.object,
theme: PropTypes.string
};
ListSeparator.displayName = 'List.Separator'; ListSeparator.displayName = 'List.Separator';
export default withTheme(ListSeparator); export default withTheme(ListSeparator);

View File

@ -2,6 +2,6 @@ import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
contentContainerStyleFlatList: { contentContainerStyleFlatList: {
paddingVertical: 32 paddingVertical: 32,
} },
}); });

View File

@ -1,8 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { Animated, Modal, StyleSheet, View } from 'react-native';
import {
StyleSheet, Modal, Animated, View
} from 'react-native';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
@ -10,26 +8,30 @@ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
}, },
image: { image: {
width: 100, width: 100,
height: 100, height: 100,
resizeMode: 'contain' resizeMode: 'contain',
} },
}); });
class Loading extends React.PureComponent { interface ILoadingProps {
static propTypes = { visible: boolean;
visible: PropTypes.bool, theme: string;
theme: PropTypes.string }
}
class Loading extends React.PureComponent<ILoadingProps, any> {
state = { state = {
scale: new Animated.Value(1), scale: new Animated.Value(1),
opacity: new Animated.Value(0) opacity: new Animated.Value(0),
} }
private opacityAnimation: any;
private scaleAnimation: any;
componentDidMount() { componentDidMount() {
const { opacity, scale } = this.state; const { opacity, scale } = this.state;
const { visible } = this.props; const { visible } = this.props;
@ -39,8 +41,8 @@ class Loading extends React.PureComponent {
{ {
toValue: 1, toValue: 1,
duration: 200, duration: 200,
useNativeDriver: true useNativeDriver: true,
} },
); );
this.scaleAnimation = Animated.loop(Animated.sequence([ this.scaleAnimation = Animated.loop(Animated.sequence([
Animated.timing( Animated.timing(
@ -48,17 +50,17 @@ class Loading extends React.PureComponent {
{ {
toValue: 0, toValue: 0,
duration: 1000, duration: 1000,
useNativeDriver: true useNativeDriver: true,
} },
), ),
Animated.timing( Animated.timing(
scale, scale,
{ {
toValue: 1, toValue: 1,
duration: 1000, duration: 1000,
useNativeDriver: true useNativeDriver: true,
} },
) ),
])); ]));
if (visible) { if (visible) {
@ -66,7 +68,7 @@ class Loading extends React.PureComponent {
} }
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: any) {
const { visible } = this.props; const { visible } = this.props;
if (visible && visible !== prevProps.visible) { if (visible && visible !== prevProps.visible) {
this.startAnimations(); this.startAnimations();
@ -97,13 +99,13 @@ class Loading extends React.PureComponent {
const scaleAnimation = scale.interpolate({ const scaleAnimation = scale.interpolate({
inputRange: [0, 0.5, 1], inputRange: [0, 0.5, 1],
outputRange: [1, 1.1, 1] outputRange: [1, 1.1, 1],
}); });
const opacityAnimation = opacity.interpolate({ const opacityAnimation = opacity.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity], outputRange: [0, themes[theme].backdropOpacity],
extrapolate: 'clamp' extrapolate: 'clamp',
}); });
return ( return (
@ -118,17 +120,18 @@ class Loading extends React.PureComponent {
> >
<Animated.View <Animated.View
style={[{ style={[{
// @ts-ignore
...StyleSheet.absoluteFill, ...StyleSheet.absoluteFill,
backgroundColor: themes[theme].backdropColor, backgroundColor: themes[theme].backdropColor,
opacity: opacityAnimation opacity: opacityAnimation,
}]} }]}
/> />
<Animated.Image <Animated.Image
source={require('../static/images/logo.png')} source={require('../static/images/logo.png')}
style={[styles.image, { style={[styles.image, {
transform: [{ transform: [{
scale: scaleAnimation scale: scaleAnimation,
}] }],
}]} }]}
/> />
</View> </View>
@ -137,4 +140,4 @@ class Loading extends React.PureComponent {
} }
} }
export default (withTheme(Loading)); export default withTheme(Loading);

View File

@ -1,8 +1,5 @@
import React from 'react'; import React from 'react';
import { import { Animated, Easing, Linking, StyleSheet, Text, View } from 'react-native';
View, StyleSheet, Text, Animated, Easing, Linking
} from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import * as AppleAuthentication from 'expo-apple-authentication'; import * as AppleAuthentication from 'expo-apple-authentication';
@ -15,7 +12,7 @@ import OrSeparator from './OrSeparator';
import Touch from '../utils/touch'; import Touch from '../utils/touch';
import I18n from '../i18n'; import I18n from '../i18n';
import random from '../utils/random'; import random from '../utils/random';
import { logEvent, events } from '../utils/log'; import { events, logEvent } from '../utils/log';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
@ -30,7 +27,7 @@ const LOGIN_STYPE_REDIRECT = 'redirect';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
serviceButton: { serviceButton: {
borderRadius: BORDER_RADIUS, borderRadius: BORDER_RADIUS,
marginBottom: 10 marginBottom: 10,
}, },
serviceButtonContainer: { serviceButtonContainer: {
borderRadius: BORDER_RADIUS, borderRadius: BORDER_RADIUS,
@ -39,46 +36,70 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingHorizontal: 15 paddingHorizontal: 15,
}, },
serviceIcon: { serviceIcon: {
position: 'absolute', position: 'absolute',
left: 15, left: 15,
top: 12, top: 12,
width: 24, width: 24,
height: 24 height: 24,
}, },
serviceText: { serviceText: {
...sharedStyles.textRegular, ...sharedStyles.textRegular,
fontSize: 16 fontSize: 16,
}, },
serviceName: { serviceName: {
...sharedStyles.textSemibold ...sharedStyles.textSemibold,
}, },
options: { options: {
marginBottom: 0 marginBottom: 0,
} },
}); });
class LoginServices extends React.PureComponent { interface IOpenOAuth {
static propTypes = { url?: string;
navigation: PropTypes.object, ssoToken?: string;
server: PropTypes.string, authType?: string;
services: PropTypes.object, }
Gitlab_URL: PropTypes.string,
CAS_enabled: PropTypes.bool, interface IService {
CAS_login_url: PropTypes.string, name: string;
separator: PropTypes.bool, service: string;
theme: PropTypes.string authType: string;
} buttonColor: string;
buttonLabelColor: string;
}
interface ILoginServicesProps {
navigation: any;
server: string;
services: {
facebook: {clientId: string;};
github: {clientId: string;};
gitlab: {clientId: string;};
google: {clientId: string;};
linkedin: {clientId: string;};
'meteor-developer': {clientId: string;};
wordpress: {clientId: string; serverURL: string;};
};
Gitlab_URL: string;
CAS_enabled: boolean;
CAS_login_url: string;
separator: boolean;
theme: string;
}
class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
private _animation: any;
static defaultProps = { static defaultProps = {
separator: true separator: true,
} }
state = { state = {
collapsed: true, collapsed: true,
servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT) servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT),
} }
onPressFacebook = () => { onPressFacebook = () => {
@ -173,11 +194,11 @@ class LoginServices extends React.PureComponent {
this.openOAuth({ url: `${ endpoint }${ params }` }); this.openOAuth({ url: `${ endpoint }${ params }` });
} }
onPressCustomOAuth = (loginService) => { onPressCustomOAuth = (loginService: any) => {
logEvent(events.ENTER_WITH_CUSTOM_OAUTH); logEvent(events.ENTER_WITH_CUSTOM_OAUTH);
const { server } = this.props; const { server } = this.props;
const { const {
serverURL, authorizePath, clientId, scope, service serverURL, authorizePath, clientId, scope, service,
} = loginService; } = loginService;
const redirectUri = `${ server }/_oauth/${ service }`; const redirectUri = `${ server }/_oauth/${ service }`;
const state = this.getOAuthState(); const state = this.getOAuthState();
@ -188,7 +209,7 @@ class LoginServices extends React.PureComponent {
this.openOAuth({ url }); this.openOAuth({ url });
} }
onPressSaml = (loginService) => { onPressSaml = (loginService: any) => {
logEvent(events.ENTER_WITH_SAML); logEvent(events.ENTER_WITH_SAML);
const { server } = this.props; const { server } = this.props;
const { clientConfig } = loginService; const { clientConfig } = loginService;
@ -206,14 +227,14 @@ class LoginServices extends React.PureComponent {
this.openOAuth({ url, ssoToken, authType: 'cas' }); this.openOAuth({ url, ssoToken, authType: 'cas' });
} }
onPressAppleLogin = async() => { onPressAppleLogin = async () => {
logEvent(events.ENTER_WITH_APPLE); logEvent(events.ENTER_WITH_APPLE);
try { try {
const { fullName, email, identityToken } = await AppleAuthentication.signInAsync({ const { fullName, email, identityToken } = await AppleAuthentication.signInAsync({
requestedScopes: [ requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME, AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL AppleAuthentication.AppleAuthenticationScope.EMAIL,
] ],
}); });
await RocketChat.loginOAuthOrSso({ fullName, email, identityToken }); await RocketChat.loginOAuthOrSso({ fullName, email, identityToken });
@ -224,30 +245,32 @@ class LoginServices extends React.PureComponent {
getOAuthState = (loginStyle = LOGIN_STYPE_POPUP) => { getOAuthState = (loginStyle = LOGIN_STYPE_POPUP) => {
const credentialToken = random(43); const credentialToken = random(43);
let obj = { loginStyle, credentialToken, isCordova: true }; let obj: any = { loginStyle, credentialToken, isCordova: true };
if (loginStyle === LOGIN_STYPE_REDIRECT) { if (loginStyle === LOGIN_STYPE_REDIRECT) {
obj = { obj = {
...obj, ...obj,
redirectUrl: 'rocketchat://auth' redirectUrl: 'rocketchat://auth',
}; };
} }
return Base64.encodeURI(JSON.stringify(obj)); return Base64.encodeURI(JSON.stringify(obj));
} }
openOAuth = ({ url, ssoToken, authType = 'oauth' }) => { openOAuth = ({ url, ssoToken, authType = 'oauth' }: IOpenOAuth) => {
const { navigation } = this.props; const { navigation } = this.props;
navigation.navigate('AuthenticationWebView', { url, authType, ssoToken }); navigation.navigate('AuthenticationWebView', { url, authType, ssoToken });
} }
transitionServicesTo = (height) => { transitionServicesTo = (height: number) => {
const { servicesHeight } = this.state; const { servicesHeight } = this.state;
if (this._animation) { if (this._animation) {
this._animation.stop(); this._animation.stop();
} }
// @ts-ignore
this._animation = Animated.timing(servicesHeight, { this._animation = Animated.timing(servicesHeight, {
toValue: height, toValue: height,
duration: 300, duration: 300,
easing: Easing.easeOutCubic // @ts-ignore
easing: Easing.easeOutCubic,
}).start(); }).start();
} }
@ -260,11 +283,11 @@ class LoginServices extends React.PureComponent {
} else { } else {
this.transitionServicesTo(SERVICES_COLLAPSED_HEIGHT); this.transitionServicesTo(SERVICES_COLLAPSED_HEIGHT);
} }
this.setState(prevState => ({ collapsed: !prevState.collapsed })); this.setState((prevState: any) => ({ collapsed: !prevState.collapsed }));
} }
getSocialOauthProvider = (name) => { getSocialOauthProvider = (name: string) => {
const oauthProviders = { const oauthProviders: any = {
facebook: this.onPressFacebook, facebook: this.onPressFacebook,
github: this.onPressGithub, github: this.onPressGithub,
gitlab: this.onPressGitlab, gitlab: this.onPressGitlab,
@ -272,7 +295,7 @@ class LoginServices extends React.PureComponent {
linkedin: this.onPressLinkedin, linkedin: this.onPressLinkedin,
'meteor-developer': this.onPressMeteor, 'meteor-developer': this.onPressMeteor,
twitter: this.onPressTwitter, twitter: this.onPressTwitter,
wordpress: this.onPressWordpress wordpress: this.onPressWordpress,
}; };
return oauthProviders[name]; return oauthProviders[name];
} }
@ -303,7 +326,7 @@ class LoginServices extends React.PureComponent {
return null; return null;
} }
renderItem = (service) => { renderItem = (service: IService) => {
const { CAS_enabled, theme } = this.props; const { CAS_enabled, theme } = this.props;
let { name } = service; let { name } = service;
name = name === 'meteor-developer' ? 'meteor' : name; name = name === 'meteor-developer' ? 'meteor' : name;
@ -373,14 +396,14 @@ class LoginServices extends React.PureComponent {
const { length } = Object.values(services); const { length } = Object.values(services);
const style = { const style = {
overflow: 'hidden', overflow: 'hidden',
height: servicesHeight height: servicesHeight,
}; };
if (length > 3 && separator) { if (length > 3 && separator) {
return ( return (
<> <>
<Animated.View style={style}> <Animated.View style={style}>
{Object.values(services).map(service => this.renderItem(service))} {Object.values(services).map((service: any) => this.renderItem(service))}
</Animated.View> </Animated.View>
{this.renderServicesSeparator()} {this.renderServicesSeparator()}
</> </>
@ -388,19 +411,19 @@ class LoginServices extends React.PureComponent {
} }
return ( return (
<> <>
{Object.values(services).map(service => this.renderItem(service))} {Object.values(services).map((service: any) => this.renderItem(service))}
{this.renderServicesSeparator()} {this.renderServicesSeparator()}
</> </>
); );
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
server: state.server.server, server: state.server.server,
Gitlab_URL: state.settings.API_Gitlab_URL, Gitlab_URL: state.settings.API_Gitlab_URL,
CAS_enabled: state.settings.CAS_enabled, CAS_enabled: state.settings.CAS_enabled,
CAS_login_url: state.settings.CAS_login_url, CAS_login_url: state.settings.CAS_login_url,
services: state.login.services services: state.login.services,
}); });
export default connect(mapStateToProps)(withTheme(LoginServices)); export default connect(mapStateToProps)(withTheme(LoginServices));

View File

@ -1,8 +1,5 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import { FlatList, StyleSheet, Text, View } from 'react-native';
import {
View, Text, FlatList, StyleSheet
} from 'react-native';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -13,6 +10,27 @@ import database from '../../lib/database';
import { Button } from '../ActionSheet'; import { Button } from '../ActionSheet';
import { useDimensions } from '../../dimensions'; import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { IEmoji } from '../EmojiPicker/interfaces';
interface IHeader {
handleReaction: Function;
server: string;
message: object;
isMasterDetail: boolean;
theme: string;
}
interface THeaderItem {
item: IEmoji;
onReaction: Function;
server: string;
theme: string;
}
interface THeaderFooter {
onReaction: any;
theme: string;
}
export const HEADER_HEIGHT = 36; export const HEADER_HEIGHT = 36;
const ITEM_SIZE = 36; const ITEM_SIZE = 36;
@ -22,7 +40,7 @@ const ITEM_MARGIN = 8;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
alignItems: 'center', alignItems: 'center',
marginHorizontal: CONTAINER_MARGIN marginHorizontal: CONTAINER_MARGIN,
}, },
headerItem: { headerItem: {
height: ITEM_SIZE, height: ITEM_SIZE,
@ -30,26 +48,24 @@ const styles = StyleSheet.create({
borderRadius: ITEM_SIZE / 2, borderRadius: ITEM_SIZE / 2,
marginHorizontal: ITEM_MARGIN, marginHorizontal: ITEM_MARGIN,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center' alignItems: 'center',
}, },
headerIcon: { headerIcon: {
...sharedStyles.textAlignCenter, ...sharedStyles.textAlignCenter,
fontSize: 20, fontSize: 20,
color: '#fff' color: '#fff',
}, },
customEmoji: { customEmoji: {
height: 20, height: 20,
width: 20 width: 20,
} },
}); });
const keyExtractor = item => item?.id || item; const keyExtractor = (item: any) => item?.id || item;
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley']; const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
const HeaderItem = React.memo(({ const HeaderItem = React.memo(({ item, onReaction, server, theme }: THeaderItem) => (
item, onReaction, server, theme
}) => (
<Button <Button
testID={`message-actions-emoji-${ item.content || item }`} testID={`message-actions-emoji-${ item.content || item }`}
onPress={() => onReaction({ emoji: `:${ item.content || item }:` })} onPress={() => onReaction({ emoji: `:${ item.content || item }:` })}
@ -65,14 +81,8 @@ const HeaderItem = React.memo(({
)} )}
</Button> </Button>
)); ));
HeaderItem.propTypes = {
item: PropTypes.string,
onReaction: PropTypes.func,
server: PropTypes.string,
theme: PropTypes.string
};
const HeaderFooter = React.memo(({ onReaction, theme }) => ( const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
<Button <Button
testID='add-reaction' testID='add-reaction'
onPress={onReaction} onPress={onReaction}
@ -82,18 +92,12 @@ const HeaderFooter = React.memo(({ onReaction, theme }) => (
<CustomIcon name='reaction-add' size={24} color={themes[theme].bodyText} /> <CustomIcon name='reaction-add' size={24} color={themes[theme].bodyText} />
</Button> </Button>
)); ));
HeaderFooter.propTypes = {
onReaction: PropTypes.func,
theme: PropTypes.string
};
const Header = React.memo(({ const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => {
handleReaction, server, message, isMasterDetail, theme
}) => {
const [items, setItems] = useState([]); const [items, setItems] = useState([]);
const { width, height } = useDimensions(); const { width, height }: any = useDimensions();
const setEmojis = async() => { const setEmojis = async () => {
try { try {
const db = database.active; const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis'); const freqEmojiCollection = db.get('frequently_used_emojis');
@ -114,11 +118,11 @@ const Header = React.memo(({
setEmojis(); setEmojis();
}, []); }, []);
const onReaction = ({ emoji }) => handleReaction(emoji, message); const onReaction = ({ emoji }: {emoji: IEmoji}) => handleReaction(emoji, message);
const renderItem = useCallback(({ item }) => <HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />); const renderItem = useCallback(({ item }) => <HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />, []);
const renderFooter = useCallback(() => <HeaderFooter onReaction={onReaction} theme={theme} />); const renderFooter = useCallback(() => <HeaderFooter onReaction={onReaction} theme={theme} />, []);
return ( return (
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}> <View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
@ -135,11 +139,5 @@ const Header = React.memo(({
</View> </View>
); );
}); });
Header.propTypes = {
handleReaction: PropTypes.func,
server: PropTypes.string,
message: PropTypes.object,
isMasterDetail: PropTypes.bool,
theme: PropTypes.string
};
export default withTheme(Header); export default withTheme(Header);

View File

@ -1,5 +1,4 @@
import React, { forwardRef, useImperativeHandle } from 'react'; import React, { forwardRef, useImperativeHandle } from 'react';
import PropTypes from 'prop-types';
import { Alert, Clipboard, Share } from 'react-native'; import { Alert, Clipboard, Share } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import moment from 'moment'; import moment from 'moment';
@ -17,6 +16,37 @@ import { useActionSheet } from '../ActionSheet';
import Header, { HEADER_HEIGHT } from './Header'; import Header, { HEADER_HEIGHT } from './Header';
import events from '../../utils/log/events'; import events from '../../utils/log/events';
interface IMessageActions {
room: {
rid: string | number;
autoTranslateLanguage: any;
autoTranslate: any;
reactWhenReadOnly: any;
};
tmid: string;
user: {
id: string | number;
};
editInit: Function;
reactionInit: Function;
onReactionPress: Function;
replyInit: Function;
isMasterDetail: boolean;
isReadOnly: boolean;
Message_AllowDeleting: boolean;
Message_AllowDeleting_BlockDeleteInMinutes: number;
Message_AllowEditing: boolean;
Message_AllowEditing_BlockEditInMinutes: number;
Message_AllowPinning: boolean;
Message_AllowStarring: boolean;
Message_Read_Receipt_Store_Users: boolean;
server: string;
editMessagePermission: [];
deleteMessagePermission: [];
forceDeleteMessagePermission: [];
pinMessagePermission: [];
}
const MessageActions = React.memo(forwardRef(({ const MessageActions = React.memo(forwardRef(({
room, room,
tmid, tmid,
@ -38,12 +68,12 @@ const MessageActions = React.memo(forwardRef(({
editMessagePermission, editMessagePermission,
deleteMessagePermission, deleteMessagePermission,
forceDeleteMessagePermission, forceDeleteMessagePermission,
pinMessagePermission pinMessagePermission,
}, ref) => { }: IMessageActions, ref): any => {
let permissions = {}; let permissions: any = {};
const { showActionSheet, hideActionSheet } = useActionSheet(); const { showActionSheet, hideActionSheet }: any = useActionSheet();
const getPermissions = async() => { const getPermissions = async () => {
try { try {
const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission]; const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission];
const result = await RocketChat.hasPermission(permission, room.rid); const result = await RocketChat.hasPermission(permission, room.rid);
@ -51,16 +81,16 @@ const MessageActions = React.memo(forwardRef(({
hasEditPermission: result[0], hasEditPermission: result[0],
hasDeletePermission: result[1], hasDeletePermission: result[1],
hasForceDeletePermission: result[2], hasForceDeletePermission: result[2],
hasPinPermission: result[3] hasPinPermission: result[3],
}; };
} catch { } catch {
// Do nothing // Do nothing
} }
}; };
const isOwn = message => message.u && message.u._id === user.id; const isOwn = (message: any) => message.u && message.u._id === user.id;
const allowEdit = (message) => { const allowEdit = (message: any) => {
if (isReadOnly) { if (isReadOnly) {
return false; return false;
} }
@ -75,7 +105,7 @@ const MessageActions = React.memo(forwardRef(({
if (message.ts != null) { if (message.ts != null) {
msgTs = moment(message.ts); msgTs = moment(message.ts);
} }
let currentTsDiff; let currentTsDiff: any;
if (msgTs != null) { if (msgTs != null) {
currentTsDiff = moment().diff(msgTs, 'minutes'); currentTsDiff = moment().diff(msgTs, 'minutes');
} }
@ -84,7 +114,7 @@ const MessageActions = React.memo(forwardRef(({
return true; return true;
}; };
const allowDelete = (message) => { const allowDelete = (message: any) => {
if (isReadOnly) { if (isReadOnly) {
return false; return false;
} }
@ -106,7 +136,7 @@ const MessageActions = React.memo(forwardRef(({
if (message.ts != null) { if (message.ts != null) {
msgTs = moment(message.ts); msgTs = moment(message.ts);
} }
let currentTsDiff; let currentTsDiff: any;
if (msgTs != null) { if (msgTs != null) {
currentTsDiff = moment().diff(msgTs, 'minutes'); currentTsDiff = moment().diff(msgTs, 'minutes');
} }
@ -115,19 +145,19 @@ const MessageActions = React.memo(forwardRef(({
return true; return true;
}; };
const getPermalink = message => RocketChat.getPermalinkMessage(message); const getPermalink = (message: any) => RocketChat.getPermalinkMessage(message);
const handleReply = (message) => { const handleReply = (message: any) => {
logEvent(events.ROOM_MSG_ACTION_REPLY); logEvent(events.ROOM_MSG_ACTION_REPLY);
replyInit(message, true); replyInit(message, true);
}; };
const handleEdit = (message) => { const handleEdit = (message: any) => {
logEvent(events.ROOM_MSG_ACTION_EDIT); logEvent(events.ROOM_MSG_ACTION_EDIT);
editInit(message); editInit(message);
}; };
const handleCreateDiscussion = (message) => { const handleCreateDiscussion = (message: any) => {
logEvent(events.ROOM_MSG_ACTION_DISCUSSION); logEvent(events.ROOM_MSG_ACTION_DISCUSSION);
const params = { message, channel: room, showCloseModal: true }; const params = { message, channel: room, showCloseModal: true };
if (isMasterDetail) { if (isMasterDetail) {
@ -137,7 +167,7 @@ const MessageActions = React.memo(forwardRef(({
} }
}; };
const handleUnread = async(message) => { const handleUnread = async (message: any) => {
logEvent(events.ROOM_MSG_ACTION_UNREAD); logEvent(events.ROOM_MSG_ACTION_UNREAD);
const { id: messageId, ts } = message; const { id: messageId, ts } = message;
const { rid } = room; const { rid } = room;
@ -147,9 +177,9 @@ const MessageActions = React.memo(forwardRef(({
if (result.success) { if (result.success) {
const subCollection = db.get('subscriptions'); const subCollection = db.get('subscriptions');
const subRecord = await subCollection.find(rid); const subRecord = await subCollection.find(rid);
await db.action(async() => { await db.action(async () => {
try { try {
await subRecord.update(sub => sub.lastOpen = ts); await subRecord.update((sub: any) => sub.lastOpen = ts);
} catch { } catch {
// do nothing // do nothing
} }
@ -162,10 +192,10 @@ const MessageActions = React.memo(forwardRef(({
} }
}; };
const handlePermalink = async(message) => { const handlePermalink = async (message: any) => {
logEvent(events.ROOM_MSG_ACTION_PERMALINK); logEvent(events.ROOM_MSG_ACTION_PERMALINK);
try { try {
const permalink = await getPermalink(message); const permalink: any = await getPermalink(message);
Clipboard.setString(permalink); Clipboard.setString(permalink);
EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') }); EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') });
} catch { } catch {
@ -173,28 +203,28 @@ const MessageActions = React.memo(forwardRef(({
} }
}; };
const handleCopy = async(message) => { const handleCopy = async (message: any) => {
logEvent(events.ROOM_MSG_ACTION_COPY); logEvent(events.ROOM_MSG_ACTION_COPY);
await Clipboard.setString(message?.attachments?.[0]?.description || message.msg); await Clipboard.setString(message?.attachments?.[0]?.description || message.msg);
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
}; };
const handleShare = async(message) => { const handleShare = async (message: any) => {
logEvent(events.ROOM_MSG_ACTION_SHARE); logEvent(events.ROOM_MSG_ACTION_SHARE);
try { try {
const permalink = await getPermalink(message); const permalink: any = await getPermalink(message);
Share.share({ message: permalink }); Share.share({ message: permalink });
} catch { } catch {
logEvent(events.ROOM_MSG_ACTION_SHARE_F); logEvent(events.ROOM_MSG_ACTION_SHARE_F);
} }
}; };
const handleQuote = (message) => { const handleQuote = (message: any) => {
logEvent(events.ROOM_MSG_ACTION_QUOTE); logEvent(events.ROOM_MSG_ACTION_QUOTE);
replyInit(message, false); replyInit(message, false);
}; };
const handleStar = async(message) => { const handleStar = async (message: any) => {
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR); logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
try { try {
await RocketChat.toggleStarMessage(message.id, message.starred); await RocketChat.toggleStarMessage(message.id, message.starred);
@ -205,7 +235,7 @@ const MessageActions = React.memo(forwardRef(({
} }
}; };
const handlePin = async(message) => { const handlePin = async (message: any) => {
logEvent(events.ROOM_MSG_ACTION_PIN); logEvent(events.ROOM_MSG_ACTION_PIN);
try { try {
await RocketChat.togglePinMessage(message.id, message.pinned); await RocketChat.togglePinMessage(message.id, message.pinned);
@ -215,7 +245,7 @@ const MessageActions = React.memo(forwardRef(({
} }
}; };
const handleReaction = (shortname, message) => { const handleReaction = (shortname: any, message: any) => {
logEvent(events.ROOM_MSG_ACTION_REACTION); logEvent(events.ROOM_MSG_ACTION_REACTION);
if (shortname) { if (shortname) {
onReactionPress(shortname, message.id); onReactionPress(shortname, message.id);
@ -226,7 +256,7 @@ const MessageActions = React.memo(forwardRef(({
hideActionSheet(); hideActionSheet();
}; };
const handleReadReceipt = (message) => { const handleReadReceipt = (message: any) => {
if (isMasterDetail) { if (isMasterDetail) {
Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } }); Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } });
} else { } else {
@ -234,11 +264,11 @@ const MessageActions = React.memo(forwardRef(({
} }
}; };
const handleToggleTranslation = async(message) => { const handleToggleTranslation = async (message: any) => {
try { try {
const db = database.active; const db = database.active;
await db.action(async() => { await db.action(async () => {
await message.update((m) => { await message.update((m: any) => {
m.autoTranslate = !m.autoTranslate; m.autoTranslate = !m.autoTranslate;
m._updatedAt = new Date(); m._updatedAt = new Date();
}); });
@ -249,7 +279,7 @@ const MessageActions = React.memo(forwardRef(({
_id: message.id, _id: message.id,
rid: message.subscription.id, rid: message.subscription.id,
u: message.u, u: message.u,
msg: message.msg msg: message.msg,
}; };
await RocketChat.translateMessage(m, room.autoTranslateLanguage); await RocketChat.translateMessage(m, room.autoTranslateLanguage);
} }
@ -258,7 +288,7 @@ const MessageActions = React.memo(forwardRef(({
} }
}; };
const handleReport = async(message) => { const handleReport = async (message: any) => {
logEvent(events.ROOM_MSG_ACTION_REPORT); logEvent(events.ROOM_MSG_ACTION_REPORT);
try { try {
await RocketChat.reportMessage(message.id); await RocketChat.reportMessage(message.id);
@ -269,11 +299,13 @@ const MessageActions = React.memo(forwardRef(({
} }
}; };
const handleDelete = (message) => { 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'),
onPress: async() => { onPress: async () => {
try { try {
logEvent(events.ROOM_MSG_ACTION_DELETE); logEvent(events.ROOM_MSG_ACTION_DELETE);
await RocketChat.deleteMessage(message.id, message.subscription.id); await RocketChat.deleteMessage(message.id, message.subscription.id);
@ -281,19 +313,19 @@ const MessageActions = React.memo(forwardRef(({
logEvent(events.ROOM_MSG_ACTION_DELETE_F); logEvent(events.ROOM_MSG_ACTION_DELETE_F);
log(e); log(e);
} }
} },
}); });
}; };
const getOptions = (message) => { const getOptions = (message: any) => {
let options = []; let options: any = [];
// Reply // Reply
if (!isReadOnly) { if (!isReadOnly) {
options = [{ options = [{
title: I18n.t('Reply_in_Thread'), title: I18n.t('Reply_in_Thread'),
icon: 'threads', icon: 'threads',
onPress: () => handleReply(message) onPress: () => handleReply(message),
}]; }];
} }
@ -302,7 +334,7 @@ const MessageActions = React.memo(forwardRef(({
options.push({ options.push({
title: I18n.t('Quote'), title: I18n.t('Quote'),
icon: 'quote', icon: 'quote',
onPress: () => handleQuote(message) onPress: () => handleQuote(message),
}); });
} }
@ -311,7 +343,7 @@ const MessageActions = React.memo(forwardRef(({
options.push({ options.push({
title: I18n.t('Edit'), title: I18n.t('Edit'),
icon: 'edit', icon: 'edit',
onPress: () => handleEdit(message) onPress: () => handleEdit(message),
}); });
} }
@ -319,14 +351,14 @@ const MessageActions = React.memo(forwardRef(({
options.push({ options.push({
title: I18n.t('Permalink'), title: I18n.t('Permalink'),
icon: 'link', icon: 'link',
onPress: () => handlePermalink(message) onPress: () => handlePermalink(message),
}); });
// Create Discussion // Create Discussion
options.push({ options.push({
title: I18n.t('Start_a_Discussion'), title: I18n.t('Start_a_Discussion'),
icon: 'discussions', icon: 'discussions',
onPress: () => handleCreateDiscussion(message) onPress: () => handleCreateDiscussion(message),
}); });
// Mark as unread // Mark as unread
@ -334,7 +366,7 @@ const MessageActions = React.memo(forwardRef(({
options.push({ options.push({
title: I18n.t('Mark_unread'), title: I18n.t('Mark_unread'),
icon: 'flag', icon: 'flag',
onPress: () => handleUnread(message) onPress: () => handleUnread(message),
}); });
} }
@ -342,14 +374,14 @@ const MessageActions = React.memo(forwardRef(({
options.push({ options.push({
title: I18n.t('Copy'), title: I18n.t('Copy'),
icon: 'copy', icon: 'copy',
onPress: () => handleCopy(message) onPress: () => handleCopy(message),
}); });
// Share // Share
options.push({ options.push({
title: I18n.t('Share'), title: I18n.t('Share'),
icon: 'share', icon: 'share',
onPress: () => handleShare(message) onPress: () => handleShare(message),
}); });
// Star // Star
@ -357,7 +389,7 @@ const MessageActions = React.memo(forwardRef(({
options.push({ options.push({
title: I18n.t(message.starred ? 'Unstar' : 'Star'), title: I18n.t(message.starred ? 'Unstar' : 'Star'),
icon: message.starred ? 'star-filled' : 'star', icon: message.starred ? 'star-filled' : 'star',
onPress: () => handleStar(message) onPress: () => handleStar(message),
}); });
} }
@ -366,7 +398,7 @@ const MessageActions = React.memo(forwardRef(({
options.push({ options.push({
title: I18n.t(message.pinned ? 'Unpin' : 'Pin'), title: I18n.t(message.pinned ? 'Unpin' : 'Pin'),
icon: 'pin', icon: 'pin',
onPress: () => handlePin(message) onPress: () => handlePin(message),
}); });
} }
@ -375,7 +407,7 @@ const MessageActions = React.memo(forwardRef(({
options.push({ options.push({
title: I18n.t('Read_Receipt'), title: I18n.t('Read_Receipt'),
icon: 'info', icon: 'info',
onPress: () => handleReadReceipt(message) onPress: () => handleReadReceipt(message),
}); });
} }
@ -384,7 +416,7 @@ const MessageActions = React.memo(forwardRef(({
options.push({ options.push({
title: I18n.t(message.autoTranslate ? 'View_Original' : 'Translate'), title: I18n.t(message.autoTranslate ? 'View_Original' : 'Translate'),
icon: 'language', icon: 'language',
onPress: () => handleToggleTranslation(message) onPress: () => handleToggleTranslation(message),
}); });
} }
@ -393,7 +425,7 @@ const MessageActions = React.memo(forwardRef(({
title: I18n.t('Report'), title: I18n.t('Report'),
icon: 'warning', icon: 'warning',
danger: true, danger: true,
onPress: () => handleReport(message) onPress: () => handleReport(message),
}); });
// Delete // Delete
@ -402,27 +434,27 @@ const MessageActions = React.memo(forwardRef(({
title: I18n.t('Delete'), title: I18n.t('Delete'),
icon: 'delete', icon: 'delete',
danger: true, danger: true,
onPress: () => handleDelete(message) onPress: () => handleDelete(message),
}); });
} }
return options; return options;
}; };
const showMessageActions = async(message) => { const showMessageActions = async (message: any) => {
logEvent(events.ROOM_SHOW_MSG_ACTIONS); logEvent(events.ROOM_SHOW_MSG_ACTIONS);
await getPermissions(); await getPermissions();
showActionSheet({ showActionSheet({
options: getOptions(message), options: getOptions(message),
headerHeight: HEADER_HEIGHT, headerHeight: HEADER_HEIGHT,
customHeader: (!isReadOnly || room.reactWhenReadOnly ? ( customHeader: !isReadOnly || room.reactWhenReadOnly ? (
<Header <Header
server={server} server={server}
handleReaction={handleReaction} handleReaction={handleReaction}
isMasterDetail={isMasterDetail} isMasterDetail={isMasterDetail}
message={message} message={message}
/> />
) : null) ) : null,
}); });
}; };
@ -430,30 +462,8 @@ const MessageActions = React.memo(forwardRef(({
return null; return null;
})); }));
MessageActions.propTypes = {
room: PropTypes.object,
tmid: PropTypes.string,
user: PropTypes.object,
editInit: PropTypes.func,
reactionInit: PropTypes.func,
onReactionPress: PropTypes.func,
replyInit: PropTypes.func,
isReadOnly: PropTypes.bool,
Message_AllowDeleting: PropTypes.bool,
Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number,
Message_AllowEditing: PropTypes.bool,
Message_AllowEditing_BlockEditInMinutes: PropTypes.number,
Message_AllowPinning: PropTypes.bool,
Message_AllowStarring: PropTypes.bool,
Message_Read_Receipt_Store_Users: PropTypes.bool,
server: PropTypes.string,
editMessagePermission: PropTypes.array,
deleteMessagePermission: PropTypes.array,
forceDeleteMessagePermission: PropTypes.array,
pinMessagePermission: PropTypes.array
};
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
server: state.server.server, server: state.server.server,
Message_AllowDeleting: state.settings.Message_AllowDeleting, Message_AllowDeleting: state.settings.Message_AllowDeleting,
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes, Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes,
@ -466,7 +476,7 @@ const mapStateToProps = state => ({
editMessagePermission: state.permissions['edit-message'], editMessagePermission: state.permissions['edit-message'],
deleteMessagePermission: state.permissions['delete-message'], deleteMessagePermission: state.permissions['delete-message'],
forceDeleteMessagePermission: state.permissions['force-delete-message'], forceDeleteMessagePermission: state.permissions['force-delete-message'],
pinMessagePermission: state.permissions['pin-message'] pinMessagePermission: state.permissions['pin-message'],
}); });
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions); export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);

View File

@ -1,5 +1,4 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { TouchableOpacity } from 'react-native'; import { TouchableOpacity } from 'react-native';
import FastImage from '@rocket.chat/react-native-fast-image'; import FastImage from '@rocket.chat/react-native-fast-image';
@ -9,7 +8,16 @@ import { themes } from '../../../constants/colors';
import MessageboxContext from '../Context'; import MessageboxContext from '../Context';
import ActivityIndicator from '../../ActivityIndicator'; import ActivityIndicator from '../../ActivityIndicator';
const Item = ({ item, theme }) => { interface IMessageBoxCommandsPreviewItem {
item: {
type: string;
id: string;
value: string;
};
theme: string;
}
const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => {
const context = useContext(MessageboxContext); const context = useContext(MessageboxContext);
const { onPressCommandPreview } = context; const { onPressCommandPreview } = context;
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@ -38,9 +46,4 @@ const Item = ({ item, theme }) => {
); );
}; };
Item.propTypes = {
item: PropTypes.object,
theme: PropTypes.string
};
export default Item; export default Item;

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import PropTypes from 'prop-types';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import Item from './Item'; import Item from './Item';
@ -8,7 +7,13 @@ import styles from '../styles';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { withTheme } from '../../../theme'; import { withTheme } from '../../../theme';
const CommandsPreview = React.memo(({ theme, commandPreview, showCommandPreview }) => { interface IMessageBoxCommandsPreview {
commandPreview: [];
showCommandPreview: boolean;
theme: string;
}
const CommandsPreview = React.memo(({ theme, commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
if (!showCommandPreview) { if (!showCommandPreview) {
return null; return null;
} }
@ -18,7 +23,7 @@ const CommandsPreview = React.memo(({ theme, commandPreview, showCommandPreview
style={[styles.mentionList, { backgroundColor: themes[theme].messageboxBackground }]} style={[styles.mentionList, { backgroundColor: themes[theme].messageboxBackground }]}
data={commandPreview} data={commandPreview}
renderItem={({ item }) => <Item item={item} theme={theme} />} renderItem={({ item }) => <Item item={item} theme={theme} />}
keyExtractor={item => item.id} keyExtractor={(item: any) => item.id}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
horizontal horizontal
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
@ -37,10 +42,4 @@ const CommandsPreview = React.memo(({ theme, commandPreview, showCommandPreview
return true; return true;
}); });
CommandsPreview.propTypes = {
commandPreview: PropTypes.array,
showCommandPreview: PropTypes.bool,
theme: PropTypes.string
};
export default withTheme(CommandsPreview); export default withTheme(CommandsPreview);

View File

@ -1,4 +0,0 @@
import React from 'react';
const MessageboxContext = React.createContext();
export default MessageboxContext;

View File

@ -0,0 +1,5 @@
import React from 'react';
// @ts-ignore
const MessageboxContext = React.createContext<any>();
export default MessageboxContext;

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { KeyboardRegistry } from 'react-native-ui-lib/keyboard'; import { KeyboardRegistry } from 'react-native-ui-lib/keyboard';
import PropTypes from 'prop-types';
import store from '../../lib/createStore'; import store from '../../lib/createStore';
import EmojiPicker from '../EmojiPicker'; import EmojiPicker from '../EmojiPicker';
@ -9,18 +8,20 @@ import styles from './styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
export default class EmojiKeyboard extends React.PureComponent { interface IMessageBoxEmojiKeyboard {
static propTypes = { theme: string
theme: PropTypes.string }
};
constructor(props) { export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiKeyboard, any> {
private readonly baseUrl: any;
constructor(props: IMessageBoxEmojiKeyboard) {
super(props); super(props);
const state = store.getState(); const state = store.getState();
this.baseUrl = state.share.server.server || state.server.server; this.baseUrl = state.share.server.server || state.server.server;
} }
onEmojiSelected = (emoji) => { onEmojiSelected = (emoji: any) => {
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji }); KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
} }

View File

@ -1,11 +1,19 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { CancelEditingButton, ToggleEmojiButton } from './buttons'; import { CancelEditingButton, ToggleEmojiButton } from './buttons';
interface IMessageBoxLeftButtons {
theme: string;
showEmojiKeyboard: boolean;
openEmoji(): void;
closeEmoji(): void;
editing: boolean;
editCancel(): void;
}
const LeftButtons = React.memo(({ const LeftButtons = React.memo(({
theme, showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji theme, showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji,
}) => { }: IMessageBoxLeftButtons) => {
if (editing) { if (editing) {
return <CancelEditingButton onPress={editCancel} theme={theme} />; return <CancelEditingButton onPress={editCancel} theme={theme} />;
} }
@ -19,13 +27,4 @@ const LeftButtons = React.memo(({
); );
}); });
LeftButtons.propTypes = {
theme: PropTypes.string,
showEmojiKeyboard: PropTypes.bool,
openEmoji: PropTypes.func.isRequired,
closeEmoji: PropTypes.func.isRequired,
editing: PropTypes.bool,
editCancel: PropTypes.func.isRequired
};
export default LeftButtons; export default LeftButtons;

View File

@ -1,13 +1,20 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native'; import { View } from 'react-native';
import { CancelEditingButton, ActionsButton } from './buttons'; import { ActionsButton, CancelEditingButton } from './buttons';
import styles from './styles'; import styles from './styles';
interface IMessageBoxLeftButtons {
theme: string;
showMessageBoxActions(): void;
editing: boolean;
editCancel(): void;
isActionsEnabled: boolean;
}
const LeftButtons = React.memo(({ const LeftButtons = React.memo(({
theme, showMessageBoxActions, editing, editCancel, isActionsEnabled theme, showMessageBoxActions, editing, editCancel, isActionsEnabled,
}) => { }: IMessageBoxLeftButtons) => {
if (editing) { if (editing) {
return <CancelEditingButton onPress={editCancel} theme={theme} />; return <CancelEditingButton onPress={editCancel} theme={theme} />;
} }
@ -17,12 +24,4 @@ const LeftButtons = React.memo(({
return <View style={styles.buttonsWhitespace} />; return <View style={styles.buttonsWhitespace} />;
}); });
LeftButtons.propTypes = {
theme: PropTypes.string,
showMessageBoxActions: PropTypes.func.isRequired,
editing: PropTypes.bool,
editCancel: PropTypes.func.isRequired,
isActionsEnabled: PropTypes.bool
};
export default LeftButtons; export default LeftButtons;

View File

@ -1,19 +1,26 @@
import React from 'react'; import React from 'react';
import { TouchableOpacity, Text } from 'react-native'; import { Text, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';
import styles from '../styles'; import styles from '../styles';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
const FixedMentionItem = ({ item, onPress, theme }) => ( interface IMessageBoxFixedMentionItem {
item: {
username: string;
};
onPress: Function;
theme: string;
}
const FixedMentionItem = ({ item, onPress, theme }: IMessageBoxFixedMentionItem) => (
<TouchableOpacity <TouchableOpacity
style={[ style={[
styles.mentionItem, styles.mentionItem,
{ {
backgroundColor: themes[theme].auxiliaryBackground, backgroundColor: themes[theme].auxiliaryBackground,
borderTopColor: themes[theme].separatorColor borderTopColor: themes[theme].separatorColor,
} },
]} ]}
onPress={() => onPress(item)} onPress={() => onPress(item)}
> >
@ -24,10 +31,4 @@ const FixedMentionItem = ({ item, onPress, theme }) => (
</TouchableOpacity> </TouchableOpacity>
); );
FixedMentionItem.propTypes = {
item: PropTypes.object,
onPress: PropTypes.func,
theme: PropTypes.string
};
export default FixedMentionItem; export default FixedMentionItem;

View File

@ -6,8 +6,13 @@ import shortnameToUnicode from '../../../utils/shortnameToUnicode';
import styles from '../styles'; import styles from '../styles';
import MessageboxContext from '../Context'; import MessageboxContext from '../Context';
import CustomEmoji from '../../EmojiPicker/CustomEmoji'; import CustomEmoji from '../../EmojiPicker/CustomEmoji';
import { IEmoji } from '../../EmojiPicker/interfaces';
const MentionEmoji = ({ item }) => { interface IMessageBoxMentionEmoji {
item: IEmoji;
}
const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
const context = useContext(MessageboxContext); const context = useContext(MessageboxContext);
const { baseUrl } = context; const { baseUrl } = context;
@ -28,7 +33,7 @@ const MentionEmoji = ({ item }) => {
}; };
MentionEmoji.propTypes = { MentionEmoji.propTypes = {
item: PropTypes.object item: PropTypes.object,
}; };
export default MentionEmoji; export default MentionEmoji;

View File

@ -1,25 +1,32 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { TouchableOpacity, Text } from 'react-native'; import { Text, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';
import styles from '../styles'; import styles from '../styles';
import Avatar from '../../Avatar'; import Avatar from '../../Avatar';
import MessageboxContext from '../Context'; import MessageboxContext from '../Context';
import FixedMentionItem from './FixedMentionItem'; import FixedMentionItem from './FixedMentionItem';
import MentionEmoji from './MentionEmoji'; import MentionEmoji from './MentionEmoji';
import { import { MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_EMOJIS } from '../constants';
MENTIONS_TRACKING_TYPE_EMOJIS,
MENTIONS_TRACKING_TYPE_COMMANDS
} from '../constants';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { IEmoji } from '../../EmojiPicker/interfaces';
const MentionItem = ({ interface IMessageBoxMentionItem {
item, trackingType, theme item: {
}) => { name: string;
command: string;
username: string;
t: string;
id: string;
} & IEmoji;
trackingType: string;
theme: string;
}
const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
const context = useContext(MessageboxContext); const context = useContext(MessageboxContext);
const { onPressMention } = context; const { onPressMention } = context;
const defineTestID = (type) => { const defineTestID = (type: string) => {
switch (type) { switch (type) {
case MENTIONS_TRACKING_TYPE_EMOJIS: case MENTIONS_TRACKING_TYPE_EMOJIS:
return `mention-item-${ item.name || item }`; return `mention-item-${ item.name || item }`;
@ -72,8 +79,8 @@ const MentionItem = ({
styles.mentionItem, styles.mentionItem,
{ {
backgroundColor: themes[theme].auxiliaryBackground, backgroundColor: themes[theme].auxiliaryBackground,
borderTopColor: themes[theme].separatorColor borderTopColor: themes[theme].separatorColor,
} },
]} ]}
onPress={() => onPressMention(item)} onPress={() => onPressMention(item)}
testID={testID} testID={testID}
@ -83,10 +90,4 @@ const MentionItem = ({
); );
}; };
MentionItem.propTypes = {
item: PropTypes.object,
trackingType: PropTypes.string,
theme: PropTypes.string
};
export default MentionItem; export default MentionItem;

View File

@ -1,13 +1,18 @@
import React from 'react'; import React from 'react';
import { FlatList, View } from 'react-native'; import { FlatList, View } from 'react-native';
import PropTypes from 'prop-types';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import styles from '../styles'; import styles from '../styles';
import MentionItem from './MentionItem'; import MentionItem from './MentionItem';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
const Mentions = React.memo(({ mentions, trackingType, theme }) => { interface IMessageBoxMentions {
mentions: [];
trackingType: string;
theme: string;
}
const Mentions = React.memo(({ mentions, trackingType, theme }: IMessageBoxMentions) => {
if (!trackingType) { if (!trackingType) {
return null; return null;
} }
@ -18,7 +23,7 @@ const Mentions = React.memo(({ mentions, trackingType, theme }) => {
data={mentions} data={mentions}
extraData={mentions} extraData={mentions}
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} theme={theme} />} renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} theme={theme} />}
keyExtractor={item => item.rid || item.name || item.command || item} keyExtractor={(item: any) => item.rid || item.name || item.command || item}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
/> />
</View> </View>
@ -36,10 +41,4 @@ const Mentions = React.memo(({ mentions, trackingType, theme }) => {
return true; return true;
}); });
Mentions.propTypes = {
mentions: PropTypes.array,
trackingType: PropTypes.string,
theme: PropTypes.string
};
export default Mentions; export default Mentions;

View File

@ -1,16 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { Text, View } from 'react-native';
import { View, Text } from 'react-native';
import { Audio } from 'expo-av'; import { Audio } from 'expo-av';
import { BorderlessButton } from 'react-native-gesture-handler'; import { BorderlessButton } from 'react-native-gesture-handler';
import { getInfoAsync } from 'expo-file-system'; import { getInfoAsync } from 'expo-file-system';
import { deactivateKeepAwake, activateKeepAwake } from 'expo-keep-awake'; import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
import styles from './styles'; import styles from './styles';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { logEvent, events } from '../../utils/log'; import { events, logEvent } from '../../utils/log';
interface IMessageBoxRecordAudioProps {
theme: string;
recordingCallback: Function;
onFinish: Function;
}
const RECORDING_EXTENSION = '.aac'; const RECORDING_EXTENSION = '.aac';
const RECORDING_SETTINGS = { const RECORDING_SETTINGS = {
@ -20,7 +25,7 @@ const RECORDING_SETTINGS = {
audioEncoder: Audio.RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC, audioEncoder: Audio.RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC,
sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.sampleRate, sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.sampleRate,
numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.numberOfChannels, numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.numberOfChannels,
bitRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.bitRate bitRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.bitRate,
}, },
ios: { ios: {
extension: RECORDING_EXTENSION, extension: RECORDING_EXTENSION,
@ -28,8 +33,8 @@ const RECORDING_SETTINGS = {
sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.ios.sampleRate, sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.ios.sampleRate,
numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.ios.numberOfChannels, numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.ios.numberOfChannels,
bitRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.ios.bitRate, bitRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.ios.bitRate,
outputFormat: Audio.RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC outputFormat: Audio.RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC,
} },
}; };
const RECORDING_MODE = { const RECORDING_MODE = {
allowsRecordingIOS: true, allowsRecordingIOS: true,
@ -38,30 +43,28 @@ const RECORDING_MODE = {
shouldDuckAndroid: true, shouldDuckAndroid: true,
playThroughEarpieceAndroid: false, playThroughEarpieceAndroid: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
}; };
const formatTime = function(seconds) { const formatTime = function(seconds: any) {
let minutes = Math.floor(seconds / 60); let minutes: any = Math.floor(seconds / 60);
seconds %= 60; seconds %= 60;
if (minutes < 10) { minutes = `0${ minutes }`; } if (minutes < 10) { minutes = `0${ minutes }`; }
if (seconds < 10) { seconds = `0${ seconds }`; } if (seconds < 10) { seconds = `0${ seconds }`; }
return `${ minutes }:${ seconds }`; return `${ minutes }:${ seconds }`;
}; };
export default class RecordAudio extends React.PureComponent { export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAudioProps, any> {
static propTypes = { private isRecorderBusy: boolean;
theme: PropTypes.string,
recordingCallback: PropTypes.func,
onFinish: PropTypes.func
}
constructor(props) { private recording: any;
constructor(props: IMessageBoxRecordAudioProps) {
super(props); super(props);
this.isRecorderBusy = false; this.isRecorderBusy = false;
this.state = { this.state = {
isRecording: false, isRecording: false,
recordingDurationMillis: 0 recordingDurationMillis: 0,
}; };
} }
@ -83,7 +86,7 @@ export default class RecordAudio extends React.PureComponent {
return formatTime(Math.floor(recordingDurationMillis / 1000)); return formatTime(Math.floor(recordingDurationMillis / 1000));
} }
isRecordingPermissionGranted = async() => { isRecordingPermissionGranted = async () => {
try { try {
const permission = await Audio.getPermissionsAsync(); const permission = await Audio.getPermissionsAsync();
if (permission.status === 'granted') { if (permission.status === 'granted') {
@ -96,14 +99,14 @@ export default class RecordAudio extends React.PureComponent {
return false; return false;
} }
onRecordingStatusUpdate = (status) => { onRecordingStatusUpdate = (status: any) => {
this.setState({ this.setState({
isRecording: status.isRecording, isRecording: status.isRecording,
recordingDurationMillis: status.durationMillis recordingDurationMillis: status.durationMillis,
}); });
} }
startRecordingAudio = async() => { startRecordingAudio = async () => {
logEvent(events.ROOM_AUDIO_RECORD); logEvent(events.ROOM_AUDIO_RECORD);
if (!this.isRecorderBusy) { if (!this.isRecorderBusy) {
this.isRecorderBusy = true; this.isRecorderBusy = true;
@ -128,7 +131,7 @@ export default class RecordAudio extends React.PureComponent {
} }
}; };
finishRecordingAudio = async() => { finishRecordingAudio = async () => {
logEvent(events.ROOM_AUDIO_FINISH); logEvent(events.ROOM_AUDIO_FINISH);
if (!this.isRecorderBusy) { if (!this.isRecorderBusy) {
const { onFinish } = this.props; const { onFinish } = this.props;
@ -145,7 +148,7 @@ export default class RecordAudio extends React.PureComponent {
type: 'audio/aac', type: 'audio/aac',
store: 'Uploads', store: 'Uploads',
path: fileURI, path: fileURI,
size: fileData.size size: fileData.size,
}; };
onFinish(fileInfo); onFinish(fileInfo);
@ -158,7 +161,7 @@ export default class RecordAudio extends React.PureComponent {
} }
}; };
cancelRecordingAudio = async() => { cancelRecordingAudio = async () => {
logEvent(events.ROOM_AUDIO_CANCEL); logEvent(events.ROOM_AUDIO_CANCEL);
if (!this.isRecorderBusy) { if (!this.isRecorderBusy) {
this.isRecorderBusy = true; this.isRecorderBusy = true;
@ -183,6 +186,7 @@ export default class RecordAudio extends React.PureComponent {
onPress={this.startRecordingAudio} onPress={this.startRecordingAudio}
style={styles.actionButton} style={styles.actionButton}
testID='messagebox-send-audio' testID='messagebox-send-audio'
// @ts-ignore
accessibilityLabel={I18n.t('Send_audio_message')} accessibilityLabel={I18n.t('Send_audio_message')}
accessibilityTraits='button' accessibilityTraits='button'
> >
@ -196,6 +200,7 @@ export default class RecordAudio extends React.PureComponent {
<View style={styles.textArea}> <View style={styles.textArea}>
<BorderlessButton <BorderlessButton
onPress={this.cancelRecordingAudio} onPress={this.cancelRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Cancel_recording')} accessibilityLabel={I18n.t('Cancel_recording')}
accessibilityTraits='button' accessibilityTraits='button'
style={styles.actionButton} style={styles.actionButton}
@ -214,6 +219,7 @@ export default class RecordAudio extends React.PureComponent {
</View> </View>
<BorderlessButton <BorderlessButton
onPress={this.finishRecordingAudio} onPress={this.finishRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Finish_recording')} accessibilityLabel={I18n.t('Finish_recording')}
accessibilityTraits='button' accessibilityTraits='button'
style={styles.actionButton} style={styles.actionButton}

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import moment from 'moment'; import moment from 'moment';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -12,38 +11,54 @@ import { themes } from '../../constants/colors';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flexDirection: 'row', flexDirection: 'row',
paddingTop: 10 paddingTop: 10,
}, },
messageContainer: { messageContainer: {
flex: 1, flex: 1,
marginHorizontal: 10, marginHorizontal: 10,
paddingHorizontal: 15, paddingHorizontal: 15,
paddingVertical: 10, paddingVertical: 10,
borderRadius: 4 borderRadius: 4,
}, },
header: { header: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'center',
}, },
username: { username: {
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium ...sharedStyles.textMedium,
}, },
time: { time: {
fontSize: 12, fontSize: 12,
lineHeight: 16, lineHeight: 16,
marginLeft: 6, marginLeft: 6,
...sharedStyles.textRegular, ...sharedStyles.textRegular,
fontWeight: '300' fontWeight: '300',
}, },
close: { close: {
marginRight: 10 marginRight: 10,
} },
}); });
interface IMessageBoxReplyPreview {
replying: boolean;
message: {
ts: Date;
msg: string;
u: any;
};
Message_TimeFormat: string;
close(): void;
baseUrl: string;
username: string;
getCustomEmoji: Function;
theme: string;
useRealName: boolean;
}
const ReplyPreview = React.memo(({ const ReplyPreview = React.memo(({
message, Message_TimeFormat, baseUrl, username, replying, getCustomEmoji, close, theme, useRealName message, Message_TimeFormat, baseUrl, username, replying, getCustomEmoji, close, theme, useRealName,
}) => { }: IMessageBoxReplyPreview) => {
if (!replying) { if (!replying) {
return null; return null;
} }
@ -53,7 +68,7 @@ const ReplyPreview = React.memo(({
<View <View
style={[ style={[
styles.container, styles.container,
{ backgroundColor: themes[theme].messageboxBackground } { backgroundColor: themes[theme].messageboxBackground },
]} ]}
> >
<View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}> <View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}>
@ -61,6 +76,7 @@ const ReplyPreview = React.memo(({
<Text style={[styles.username, { color: themes[theme].tintColor }]}>{useRealName ? message.u?.name : message.u?.username}</Text> <Text style={[styles.username, { color: themes[theme].tintColor }]}>{useRealName ? message.u?.name : message.u?.username}</Text>
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View> </View>
{/* @ts-ignore*/}
<Markdown <Markdown
msg={message.msg} msg={message.msg}
baseUrl={baseUrl} baseUrl={baseUrl}
@ -74,24 +90,12 @@ const ReplyPreview = React.memo(({
<CustomIcon name='close' color={themes[theme].auxiliaryText} size={20} style={styles.close} onPress={close} /> <CustomIcon name='close' color={themes[theme].auxiliaryText} size={20} style={styles.close} onPress={close} />
</View> </View>
); );
}, (prevProps, nextProps) => prevProps.replying === nextProps.replying && prevProps.theme === nextProps.theme && prevProps.message.id === nextProps.message.id); }, (prevProps: any, nextProps: any) => prevProps.replying === nextProps.replying && prevProps.theme === nextProps.theme && prevProps.message.id === nextProps.message.id);
ReplyPreview.propTypes = { const mapStateToProps = (state: any) => ({
replying: PropTypes.bool,
message: PropTypes.object.isRequired,
Message_TimeFormat: PropTypes.string.isRequired,
close: PropTypes.func.isRequired,
baseUrl: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
getCustomEmoji: PropTypes.func,
theme: PropTypes.string,
useRealName: PropTypes.bool
};
const mapStateToProps = state => ({
Message_TimeFormat: state.settings.Message_TimeFormat, Message_TimeFormat: state.settings.Message_TimeFormat,
baseUrl: state.server.server, baseUrl: state.server.server,
useRealName: state.settings.UI_Use_Real_Name useRealName: state.settings.UI_Use_Real_Name,
}); });
export default connect(mapStateToProps)(ReplyPreview); export default connect(mapStateToProps)(ReplyPreview);

View File

@ -1,13 +1,20 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native'; import { View } from 'react-native';
import { SendButton, ActionsButton } from './buttons'; import { ActionsButton, SendButton } from './buttons';
import styles from './styles'; import styles from './styles';
interface IMessageBoxRightButtons {
theme: string;
showSend: boolean;
submit(): void;
showMessageBoxActions(): void;
isActionsEnabled: boolean;
}
const RightButtons = React.memo(({ const RightButtons = React.memo(({
theme, showSend, submit, showMessageBoxActions, isActionsEnabled theme, showSend, submit, showMessageBoxActions, isActionsEnabled,
}) => { }: IMessageBoxRightButtons) => {
if (showSend) { if (showSend) {
return <SendButton onPress={submit} theme={theme} />; return <SendButton onPress={submit} theme={theme} />;
} }
@ -18,12 +25,4 @@ const RightButtons = React.memo(({
return <View style={styles.buttonsWhitespace} />; return <View style={styles.buttonsWhitespace} />;
}); });
RightButtons.propTypes = {
theme: PropTypes.string,
showSend: PropTypes.bool,
submit: PropTypes.func.isRequired,
showMessageBoxActions: PropTypes.func.isRequired,
isActionsEnabled: PropTypes.bool
};
export default RightButtons; export default RightButtons;

View File

@ -1,19 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { SendButton } from './buttons';
const RightButtons = React.memo(({ theme, showSend, submit }) => {
if (showSend) {
return <SendButton theme={theme} onPress={submit} />;
}
return null;
});
RightButtons.propTypes = {
theme: PropTypes.string,
showSend: PropTypes.bool,
submit: PropTypes.func.isRequired
};
export default RightButtons;

View File

@ -0,0 +1,18 @@
import React from 'react';
import { SendButton } from './buttons';
interface IMessageBoxRightButtons {
theme: string;
showSend: boolean;
submit(): void;
}
const RightButtons = React.memo(({ theme, showSend, submit }: IMessageBoxRightButtons) => {
if (showSend) {
return <SendButton theme={theme} onPress={submit} />;
}
return null;
});
export default RightButtons;

View File

@ -1,9 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import BaseButton from './BaseButton'; import BaseButton from './BaseButton';
const ActionsButton = React.memo(({ theme, onPress }) => ( interface IActionsButton {
theme: string;
onPress(): void;
}
const ActionsButton = React.memo(({ theme, onPress }: IActionsButton) => (
<BaseButton <BaseButton
onPress={onPress} onPress={onPress}
testID='messagebox-actions' testID='messagebox-actions'
@ -13,9 +17,4 @@ const ActionsButton = React.memo(({ theme, onPress }) => (
/> />
)); ));
ActionsButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired
};
export default ActionsButton; export default ActionsButton;

View File

@ -1,33 +1,31 @@
import React from 'react'; import React from 'react';
import { BorderlessButton } from 'react-native-gesture-handler'; import { BorderlessButton } from 'react-native-gesture-handler';
import PropTypes from 'prop-types';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
import styles from '../styles'; import styles from '../styles';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
const BaseButton = React.memo(({ interface IBaseButton {
onPress, testID, accessibilityLabel, icon, theme, color theme: string;
}) => ( onPress(): void;
testID: string;
accessibilityLabel: string;
icon: string;
color: string;
}
const BaseButton = React.memo(({ onPress, testID, accessibilityLabel, icon, theme, color }: Partial<IBaseButton>) => (
<BorderlessButton <BorderlessButton
onPress={onPress} onPress={onPress}
style={styles.actionButton} style={styles.actionButton}
testID={testID} testID={testID}
// @ts-ignore
accessibilityLabel={I18n.t(accessibilityLabel)} accessibilityLabel={I18n.t(accessibilityLabel)}
accessibilityTraits='button' accessibilityTraits='button'
> >
<CustomIcon name={icon} size={24} color={color ?? themes[theme].auxiliaryTintColor} /> <CustomIcon name={icon} size={24} color={color ?? themes[theme!].auxiliaryTintColor} />
</BorderlessButton> </BorderlessButton>
)); ));
BaseButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired,
accessibilityLabel: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
color: PropTypes.string
};
export default BaseButton; export default BaseButton;

View File

@ -1,9 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import BaseButton from './BaseButton'; import BaseButton from './BaseButton';
const CancelEditingButton = React.memo(({ theme, onPress }) => ( interface ICancelEditingButton {
theme: string;
onPress():void;
}
const CancelEditingButton = React.memo(({ theme, onPress }: ICancelEditingButton) => (
<BaseButton <BaseButton
onPress={onPress} onPress={onPress}
testID='messagebox-cancel-editing' testID='messagebox-cancel-editing'
@ -13,9 +17,4 @@ const CancelEditingButton = React.memo(({ theme, onPress }) => (
/> />
)); ));
CancelEditingButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired
};
export default CancelEditingButton; export default CancelEditingButton;

View File

@ -1,10 +1,14 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import BaseButton from './BaseButton'; import BaseButton from './BaseButton';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
const SendButton = React.memo(({ theme, onPress }) => ( interface ISendButton {
theme: string;
onPress(): void;
}
const SendButton = React.memo(({ theme, onPress }: ISendButton) => (
<BaseButton <BaseButton
onPress={onPress} onPress={onPress}
testID='messagebox-send-message' testID='messagebox-send-message'
@ -15,9 +19,4 @@ const SendButton = React.memo(({ theme, onPress }) => (
/> />
)); ));
SendButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired
};
export default SendButton; export default SendButton;

View File

@ -1,11 +1,15 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import BaseButton from './BaseButton'; import BaseButton from './BaseButton';
const ToggleEmojiButton = React.memo(({ interface IToggleEmojiButton {
theme, show, open, close theme: string;
}) => { show: boolean;
open(): void;
close(): void;
}
const ToggleEmojiButton = React.memo(({ theme, show, open, close }: IToggleEmojiButton) => {
if (show) { if (show) {
return ( return (
<BaseButton <BaseButton
@ -28,11 +32,4 @@ const ToggleEmojiButton = React.memo(({
); );
}); });
ToggleEmojiButton.propTypes = {
theme: PropTypes.string,
show: PropTypes.bool,
open: PropTypes.func.isRequired,
close: PropTypes.func.isRequired
};
export default ToggleEmojiButton; export default ToggleEmojiButton;

View File

@ -7,5 +7,5 @@ export {
CancelEditingButton, CancelEditingButton,
ToggleEmojiButton, ToggleEmojiButton,
SendButton, SendButton,
ActionsButton ActionsButton,
}; };

View File

@ -1,8 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import { Alert, Keyboard, NativeModules, Text, View } from 'react-native';
import {
View, Alert, Keyboard, NativeModules, Text
} from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard'; import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker from 'react-native-image-crop-picker'; import ImagePicker from 'react-native-image-crop-picker';
@ -18,31 +15,35 @@ import RocketChat from '../../lib/rocketchat';
import styles from './styles'; import styles from './styles';
import database from '../../lib/database'; import database from '../../lib/database';
import { emojis } from '../../emojis'; import { emojis } from '../../emojis';
import log, { logEvent, events } from '../../utils/log'; import log, { events, logEvent } from '../../utils/log';
import RecordAudio from './RecordAudio'; import RecordAudio from './RecordAudio';
import I18n from '../../i18n'; import I18n from '../../i18n';
import ReplyPreview from './ReplyPreview'; import ReplyPreview from './ReplyPreview';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
// @ts-ignore
// eslint-disable-next-line import/extensions,import/no-unresolved
import LeftButtons from './LeftButtons'; import LeftButtons from './LeftButtons';
// @ts-ignore
// eslint-disable-next-line import/extensions,import/no-unresolved
import RightButtons from './RightButtons'; import RightButtons from './RightButtons';
import { isAndroid, isTablet } from '../../utils/deviceInfo'; import { isAndroid, isTablet } from '../../utils/deviceInfo';
import { canUploadFile } from '../../utils/media'; import { canUploadFile } from '../../utils/media';
import EventEmiter from '../../utils/events'; import EventEmiter from '../../utils/events';
import { import {
KEY_COMMAND, KEY_COMMAND,
handleCommandTyping, handleCommandShowUpload,
handleCommandSubmit, handleCommandSubmit,
handleCommandShowUpload handleCommandTyping,
} from '../../commands'; } from '../../commands';
import Mentions from './Mentions'; import Mentions from './Mentions';
import MessageboxContext from './Context'; import MessageboxContext from './Context';
import { import {
MENTIONS_TRACKING_TYPE_EMOJIS,
MENTIONS_TRACKING_TYPE_COMMANDS,
MENTIONS_COUNT_TO_DISPLAY, MENTIONS_COUNT_TO_DISPLAY,
MENTIONS_TRACKING_TYPE_COMMANDS,
MENTIONS_TRACKING_TYPE_EMOJIS,
MENTIONS_TRACKING_TYPE_ROOMS,
MENTIONS_TRACKING_TYPE_USERS, MENTIONS_TRACKING_TYPE_USERS,
MENTIONS_TRACKING_TYPE_ROOMS
} from './constants'; } from './constants';
import CommandsPreview from './CommandsPreview'; import CommandsPreview from './CommandsPreview';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
@ -59,67 +60,116 @@ const imagePickerConfig = {
cropping: true, cropping: true,
compressImageQuality: 0.8, compressImageQuality: 0.8,
avoidEmptySpaceAroundImage: false, avoidEmptySpaceAroundImage: false,
freeStyleCropEnabled: true freeStyleCropEnabled: true,
}; };
const libraryPickerConfig = { const libraryPickerConfig = {
multiple: true, multiple: true,
compressVideoPreset: 'Passthrough', compressVideoPreset: 'Passthrough',
mediaType: 'any' mediaType: 'any',
}; };
const videoPickerConfig = { const videoPickerConfig = {
mediaType: 'video' mediaType: 'video',
}; };
class MessageBox extends Component { interface IMessageBoxProps {
static propTypes = { rid: string;
rid: PropTypes.string.isRequired, baseUrl: string;
baseUrl: PropTypes.string.isRequired, message: {
message: PropTypes.object, u: {
replying: PropTypes.bool, username: string;
editing: PropTypes.bool, };
threadsEnabled: PropTypes.bool, id: any;
isFocused: PropTypes.func, };
user: PropTypes.shape({ replying: boolean;
id: PropTypes.string, editing: boolean;
username: PropTypes.string, threadsEnabled: boolean;
token: PropTypes.string isFocused(): boolean;
}), user: {
roomType: PropTypes.string, id: string;
tmid: PropTypes.string, username: string;
replyWithMention: PropTypes.bool, token: string;
FileUpload_MediaTypeWhiteList: PropTypes.string, };
FileUpload_MaxFileSize: PropTypes.number, roomType: string;
Message_AudioRecorderEnabled: PropTypes.bool, tmid: string;
getCustomEmoji: PropTypes.func, replyWithMention: boolean;
editCancel: PropTypes.func.isRequired, FileUpload_MediaTypeWhiteList: string;
editRequest: PropTypes.func.isRequired, FileUpload_MaxFileSize: number;
onSubmit: PropTypes.func.isRequired, Message_AudioRecorderEnabled: boolean;
typing: PropTypes.func, getCustomEmoji: Function;
theme: PropTypes.string, editCancel: Function;
replyCancel: PropTypes.func, editRequest: Function;
showSend: PropTypes.bool, onSubmit: Function;
navigation: PropTypes.object, typing: Function;
children: PropTypes.node, theme: string;
isMasterDetail: PropTypes.bool, replyCancel(): void;
showActionSheet: PropTypes.func, showSend: boolean;
iOSScrollBehavior: PropTypes.number, navigation: any;
sharing: PropTypes.bool, children: JSX.Element;
isActionsEnabled: PropTypes.bool isMasterDetail: boolean;
} showActionSheet: Function;
iOSScrollBehavior: number;
sharing: boolean;
isActionsEnabled: boolean;
}
interface IMessageBoxState {
mentions: any[];
showEmojiKeyboard: boolean;
showSend: any;
recording: boolean;
trackingType: string;
commandPreview: [];
showCommandPreview: boolean;
command: {
appId?: any
};
tshow: boolean;
}
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
private text: string;
private selection: { start: number; end: number };
private focused: boolean;
private options: any;
private imagePickerConfig: any;
private libraryPickerConfig: any;
private videoPickerConfig: any;
private room: any;
private thread: any;
private unsubscribeFocus: any;
private trackingTimeout: any;
private tracking: any;
private unsubscribeBlur: any;
private component: any;
private typingTimeout: any;
static defaultProps = { static defaultProps = {
message: { message: {
id: '' id: '',
}, },
sharing: false, sharing: false,
iOSScrollBehavior: NativeModules.KeyboardTrackingViewTempManager?.KeyboardTrackingScrollBehaviorFixedOffset, iOSScrollBehavior: NativeModules.KeyboardTrackingViewTempManager?.KeyboardTrackingScrollBehaviorFixedOffset,
isActionsEnabled: true, isActionsEnabled: true,
getCustomEmoji: () => {} getCustomEmoji: () => {},
} }
constructor(props) { constructor(props: IMessageBoxProps) {
super(props); super(props);
this.state = { this.state = {
mentions: [], mentions: [],
@ -130,7 +180,7 @@ class MessageBox extends Component {
commandPreview: [], commandPreview: [],
showCommandPreview: false, showCommandPreview: false,
command: {}, command: {},
tshow: false tshow: false,
}; };
this.text = ''; this.text = '';
this.selection = { start: 0, end: 0 }; this.selection = { start: 0, end: 0 };
@ -141,54 +191,55 @@ class MessageBox extends Component {
{ {
title: I18n.t('Take_a_photo'), title: I18n.t('Take_a_photo'),
icon: 'camera-photo', icon: 'camera-photo',
onPress: this.takePhoto onPress: this.takePhoto,
}, },
{ {
title: I18n.t('Take_a_video'), title: I18n.t('Take_a_video'),
icon: 'camera', icon: 'camera',
onPress: this.takeVideo onPress: this.takeVideo,
}, },
{ {
title: I18n.t('Choose_from_library'), title: I18n.t('Choose_from_library'),
icon: 'image', icon: 'image',
onPress: this.chooseFromLibrary onPress: this.chooseFromLibrary,
}, },
{ {
title: I18n.t('Choose_file'), title: I18n.t('Choose_file'),
icon: 'attach', icon: 'attach',
onPress: this.chooseFile onPress: this.chooseFile,
}, },
{ {
title: I18n.t('Create_Discussion'), title: I18n.t('Create_Discussion'),
icon: 'discussions', icon: 'discussions',
onPress: this.createDiscussion onPress: this.createDiscussion,
} },
]; ];
const libPickerLabels = { const libPickerLabels = {
cropperChooseText: I18n.t('Choose'), cropperChooseText: I18n.t('Choose'),
cropperCancelText: I18n.t('Cancel'), cropperCancelText: I18n.t('Cancel'),
loadingLabelText: I18n.t('Processing') loadingLabelText: I18n.t('Processing'),
}; };
this.imagePickerConfig = { this.imagePickerConfig = {
...imagePickerConfig, ...imagePickerConfig,
...libPickerLabels ...libPickerLabels,
}; };
this.libraryPickerConfig = { this.libraryPickerConfig = {
...libraryPickerConfig, ...libraryPickerConfig,
...libPickerLabels ...libPickerLabels,
}; };
this.videoPickerConfig = { this.videoPickerConfig = {
...videoPickerConfig, ...videoPickerConfig,
...libPickerLabels ...libPickerLabels,
}; };
} }
async componentDidMount() { async componentDidMount() {
const db = database.active; const db = database.active;
const { const { rid, tmid, navigation, sharing } = this.props;
rid, tmid, navigation, sharing
} = this.props;
let msg; let msg;
try { try {
const threadsCollection = db.get('threads'); const threadsCollection = db.get('threads');
@ -238,10 +289,8 @@ class MessageBox extends Component {
}); });
} }
UNSAFE_componentWillReceiveProps(nextProps) { UNSAFE_componentWillReceiveProps(nextProps: any) {
const { const { isFocused, editing, replying, sharing } = this.props;
isFocused, editing, replying, sharing
} = this.props;
if (!isFocused?.()) { if (!isFocused?.()) {
return; return;
} }
@ -266,14 +315,15 @@ class MessageBox extends Component {
} }
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: any, nextState: any) {
const { const {
showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow,
} = this.state; } = this.state;
const { const {
roomType, replying, editing, isFocused, message, theme roomType, replying, editing, isFocused, message, theme,
} = this.props; } = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
return true; return true;
} }
@ -341,19 +391,19 @@ class MessageBox extends Component {
} }
} }
onChangeText = (text) => { onChangeText: any = (text: string): void => {
const isTextEmpty = text.length === 0; const isTextEmpty = text.length === 0;
this.setShowSend(!isTextEmpty); this.setShowSend(!isTextEmpty);
this.debouncedOnChangeText(text); this.debouncedOnChangeText(text);
this.setInput(text); this.setInput(text);
} }
onSelectionChange = (e) => { onSelectionChange = (e: any) => {
this.selection = e.nativeEvent.selection; this.selection = e.nativeEvent.selection;
} }
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
debouncedOnChangeText = debounce(async(text) => { debouncedOnChangeText = debounce(async (text: any) => {
const { sharing } = this.props; const { sharing } = this.props;
const isTextEmpty = text.length === 0; const isTextEmpty = text.length === 0;
if (isTextEmpty) { if (isTextEmpty) {
@ -389,22 +439,21 @@ class MessageBox extends Component {
} }
} }
return this.identifyMentionKeyword(command, MENTIONS_TRACKING_TYPE_COMMANDS); return this.identifyMentionKeyword(command, MENTIONS_TRACKING_TYPE_COMMANDS);
} else if (channelMention) { } if (channelMention) {
return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_ROOMS); return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_ROOMS);
} else if (userMention) { } if (userMention) {
return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_USERS); return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_USERS);
} else if (emojiMention) { } if (emojiMention) {
return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_EMOJIS); return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_EMOJIS);
} else {
return this.stopTrackingMention();
} }
return this.stopTrackingMention();
}, 100) }, 100)
onKeyboardResigned = () => { onKeyboardResigned = () => {
this.closeEmoji(); this.closeEmoji();
} }
onPressMention = (item) => { onPressMention = (item: any) => {
if (!this.component) { if (!this.component) {
return; return;
} }
@ -416,21 +465,23 @@ class MessageBox extends Component {
const result = msg.substr(0, cursor).replace(regexp, ''); const result = msg.substr(0, cursor).replace(regexp, '');
const mentionName = trackingType === MENTIONS_TRACKING_TYPE_EMOJIS const mentionName = trackingType === MENTIONS_TRACKING_TYPE_EMOJIS
? `${ item.name || item }:` ? `${ item.name || item }:`
: (item.username || item.name || item.command); : item.username || item.name || item.command;
const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`; const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
if ((trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) && item.providesPreview) { if ((trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) && item.providesPreview) {
this.setState({ showCommandPreview: true }); this.setState({ showCommandPreview: true });
} }
const newCursor = cursor + mentionName.length; const newCursor = cursor + mentionName.length;
this.setInput(text, { start: newCursor, end: newCursor }); this.setInput(text, { start: newCursor, end: newCursor });
this.focus(); this.focus();
requestAnimationFrame(() => this.stopTrackingMention()); requestAnimationFrame(() => this.stopTrackingMention());
} }
onPressCommandPreview = (item) => { onPressCommandPreview = (item: any) => {
const { command } = this.state; const { command } = this.state;
const { const {
rid, tmid, message: { id: messageTmid }, replyCancel rid, tmid, message: { id: messageTmid }, replyCancel,
} = this.props; } = this.props;
const { text } = this; const { text } = this;
const name = text.substr(0, text.indexOf(' ')).slice(1); const name = text.substr(0, text.indexOf(' ')).slice(1);
@ -449,7 +500,7 @@ class MessageBox extends Component {
} }
} }
onEmojiSelected = (keyboardId, params) => { onEmojiSelected = (keyboardId: any, params: any) => {
const { text } = this; const { text } = this;
const { emoji } = params; const { emoji } = params;
let newText = ''; let newText = '';
@ -463,7 +514,7 @@ class MessageBox extends Component {
this.setShowSend(true); this.setShowSend(true);
} }
getPermalink = async(message) => { getPermalink = async (message: any) => {
try { try {
return await RocketChat.getPermalinkMessage(message); return await RocketChat.getPermalinkMessage(message);
} catch (error) { } catch (error) {
@ -471,8 +522,8 @@ class MessageBox extends Component {
} }
} }
getFixedMentions = (keyword) => { getFixedMentions = (keyword: any) => {
let result = []; let result: any = [];
if ('all'.indexOf(keyword) !== -1) { if ('all'.indexOf(keyword) !== -1) {
result = [{ rid: -1, username: 'all' }]; result = [{ rid: -1, username: 'all' }];
} }
@ -482,18 +533,18 @@ class MessageBox extends Component {
return result; return result;
} }
getUsers = debounce(async(keyword) => { getUsers = debounce(async (keyword: any) => {
let res = await RocketChat.search({ text: keyword, filterRooms: false, filterUsers: true }); let res = await RocketChat.search({ text: keyword, filterRooms: false, filterUsers: true });
res = [...this.getFixedMentions(keyword), ...res]; res = [...this.getFixedMentions(keyword), ...res];
this.setState({ mentions: res }); this.setState({ mentions: res });
}, 300) }, 300)
getRooms = debounce(async(keyword = '') => { getRooms = debounce(async (keyword = '') => {
const res = await RocketChat.search({ text: keyword, filterRooms: true, filterUsers: false }); const res = await RocketChat.search({ text: keyword, filterRooms: true, filterUsers: false });
this.setState({ mentions: res }); this.setState({ mentions: res });
}, 300) }, 300)
getEmojis = debounce(async(keyword) => { getEmojis = debounce(async (keyword: any) => {
const db = database.active; const db = database.active;
const customEmojisCollection = db.get('custom_emojis'); const customEmojisCollection = db.get('custom_emojis');
const likeString = sanitizeLikeString(keyword); const likeString = sanitizeLikeString(keyword);
@ -503,17 +554,17 @@ class MessageBox extends Component {
} }
let customEmojis = await customEmojisCollection.query(...whereClause).fetch(); let customEmojis = await customEmojisCollection.query(...whereClause).fetch();
customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY); customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY);
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY); const filteredEmojis = emojis.filter((emoji) => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY); const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
this.setState({ mentions: mergedEmojis || [] }); this.setState({ mentions: mergedEmojis || [] });
}, 300) }, 300)
getSlashCommands = debounce(async(keyword) => { getSlashCommands = debounce(async (keyword: any) => {
const db = database.active; const db = database.active;
const commandsCollection = db.get('slash_commands'); const commandsCollection = db.get('slash_commands');
const likeString = sanitizeLikeString(keyword); const likeString = sanitizeLikeString(keyword);
const commands = await commandsCollection.query( const commands = await commandsCollection.query(
Q.where('id', Q.like(`${ likeString }%`)) Q.where('id', Q.like(`${ likeString }%`)),
).fetch(); ).fetch();
this.setState({ mentions: commands || [] }); this.setState({ mentions: commands || [] });
}, 300) }, 300)
@ -524,7 +575,7 @@ class MessageBox extends Component {
} }
} }
handleTyping = (isTyping) => { handleTyping = (isTyping: boolean) => {
const { typing, rid, sharing } = this.props; const { typing, rid, sharing } = this.props;
if (sharing) { if (sharing) {
return; return;
@ -548,7 +599,7 @@ class MessageBox extends Component {
}, 1000); }, 1000);
} }
setCommandPreview = async(command, name, params) => { setCommandPreview = async (command: any, name: string, params: any) => {
const { rid } = this.props; const { rid } = this.props;
try { try {
const { success, preview } = await RocketChat.getCommandPreview(name, rid, params); const { success, preview } = await RocketChat.getCommandPreview(name, rid, params);
@ -561,7 +612,7 @@ class MessageBox extends Component {
this.setState({ commandPreview: [], showCommandPreview: true, command: {} }); this.setState({ commandPreview: [], showCommandPreview: true, command: {} });
} }
setInput = (text, selection) => { setInput = (text: any, selection?: any) => {
this.text = text; this.text = text;
if (selection) { if (selection) {
return this.component.setTextAndSelection(text, selection); return this.component.setTextAndSelection(text, selection);
@ -569,7 +620,7 @@ class MessageBox extends Component {
this.component.setNativeProps({ text }); this.component.setNativeProps({ text });
} }
setShowSend = (showSend) => { setShowSend = (showSend: any) => {
const { showSend: prevShowSend } = this.state; const { showSend: prevShowSend } = this.state;
const { showSend: propShowSend } = this.props; const { showSend: propShowSend } = this.props;
if (prevShowSend !== showSend && !propShowSend) { if (prevShowSend !== showSend && !propShowSend) {
@ -583,7 +634,7 @@ class MessageBox extends Component {
this.setState({ tshow: false }); this.setState({ tshow: false });
} }
canUploadFile = (file) => { canUploadFile = (file: any) => {
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props; const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props;
const result = canUploadFile(file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize); const result = canUploadFile(file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize);
if (result.success) { if (result.success) {
@ -593,7 +644,7 @@ class MessageBox extends Component {
return false; return false;
} }
takePhoto = async() => { takePhoto = async () => {
logEvent(events.ROOM_BOX_ACTION_PHOTO); logEvent(events.ROOM_BOX_ACTION_PHOTO);
try { try {
const image = await ImagePicker.openCamera(this.imagePickerConfig); const image = await ImagePicker.openCamera(this.imagePickerConfig);
@ -605,7 +656,7 @@ class MessageBox extends Component {
} }
} }
takeVideo = async() => { takeVideo = async () => {
logEvent(events.ROOM_BOX_ACTION_VIDEO); logEvent(events.ROOM_BOX_ACTION_VIDEO);
try { try {
const video = await ImagePicker.openCamera(this.videoPickerConfig); const video = await ImagePicker.openCamera(this.videoPickerConfig);
@ -617,7 +668,7 @@ class MessageBox extends Component {
} }
} }
chooseFromLibrary = async() => { chooseFromLibrary = async () => {
logEvent(events.ROOM_BOX_ACTION_LIBRARY); logEvent(events.ROOM_BOX_ACTION_LIBRARY);
try { try {
const attachments = await ImagePicker.openPicker(this.libraryPickerConfig); const attachments = await ImagePicker.openPicker(this.libraryPickerConfig);
@ -627,17 +678,17 @@ class MessageBox extends Component {
} }
} }
chooseFile = async() => { chooseFile = async () => {
logEvent(events.ROOM_BOX_ACTION_FILE); logEvent(events.ROOM_BOX_ACTION_FILE);
try { try {
const res = await DocumentPicker.pick({ const res = await DocumentPicker.pick({
type: [DocumentPicker.types.allFiles] type: [DocumentPicker.types.allFiles],
}); });
const file = { const file = {
filename: res.name, filename: res.name,
size: res.size, size: res.size,
mime: res.type, mime: res.type,
path: res.uri path: res.uri,
}; };
if (this.canUploadFile(file)) { if (this.canUploadFile(file)) {
this.openShareView([file]); this.openShareView([file]);
@ -650,7 +701,7 @@ class MessageBox extends Component {
} }
} }
openShareView = (attachments) => { openShareView = (attachments: any) => {
const { message, replyCancel, replyWithMention } = this.props; const { message, replyCancel, replyWithMention } = this.props;
// Start a thread with an attachment // Start a thread with an attachment
let { thread } = this; let { thread } = this;
@ -689,13 +740,13 @@ class MessageBox extends Component {
this.setState({ showEmojiKeyboard: true }); this.setState({ showEmojiKeyboard: true });
} }
recordingCallback = (recording) => { recordingCallback = (recording: any) => {
this.setState({ recording }); this.setState({ recording });
} }
finishAudioMessage = async(fileInfo) => { finishAudioMessage = async (fileInfo: any) => {
const { const {
rid, tmid, baseUrl: server, user rid, tmid, baseUrl: server, user,
} = this.props; } = this.props;
if (fileInfo) { if (fileInfo) {
@ -713,10 +764,10 @@ class MessageBox extends Component {
this.setState({ showEmojiKeyboard: false }); this.setState({ showEmojiKeyboard: false });
} }
submit = async() => { submit = async () => {
const { tshow } = this.state; const { tshow } = this.state;
const { const {
onSubmit, rid: roomId, tmid, showSend, sharing onSubmit, rid: roomId, tmid, showSend, sharing,
} = this.props; } = this.props;
const message = this.text; const message = this.text;
@ -736,7 +787,7 @@ class MessageBox extends Component {
} }
const { const {
editing, replying, message: { id: messageTmid }, replyCancel editing, replying, message: { id: messageTmid }, replyCancel,
} = this.props; } = this.props;
// Slash command // Slash command
@ -746,7 +797,7 @@ class MessageBox extends Component {
const command = message.replace(/ .*/, '').slice(1); const command = message.replace(/ .*/, '').slice(1);
const likeString = sanitizeLikeString(command); const likeString = sanitizeLikeString(command);
const slashCommand = await commandsCollection.query( const slashCommand = await commandsCollection.query(
Q.where('id', Q.like(`${ likeString }%`)) Q.where('id', Q.like(`${ likeString }%`)),
).fetch(); ).fetch();
if (slashCommand.length > 0) { if (slashCommand.length > 0) {
logEvent(events.COMMAND_RUN); logEvent(events.COMMAND_RUN);
@ -767,13 +818,14 @@ class MessageBox extends Component {
// Edit // Edit
if (editing) { if (editing) {
const { message: editingMessage, editRequest } = this.props; const { message: editingMessage, editRequest } = this.props;
// @ts-ignore
const { id, subscription: { id: rid } } = editingMessage; const { id, subscription: { id: rid } } = editingMessage;
editRequest({ id, msg: message, rid }); editRequest({ id, msg: message, rid });
// Reply // Reply
} else if (replying) { } else if (replying) {
const { const {
message: replyingMessage, threadsEnabled, replyWithMention message: replyingMessage, threadsEnabled, replyWithMention,
} = this.props; } = this.props;
// Thread // Thread
@ -798,11 +850,12 @@ class MessageBox extends Component {
// Normal message // Normal message
} else { } else {
// @ts-ignore
onSubmit(message, undefined, tshow); onSubmit(message, undefined, tshow);
} }
} }
updateMentions = (keyword, type) => { updateMentions = (keyword: any, type: string) => {
if (type === MENTIONS_TRACKING_TYPE_USERS) { if (type === MENTIONS_TRACKING_TYPE_USERS) {
this.getUsers(keyword); this.getUsers(keyword);
} else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) { } else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
@ -814,10 +867,10 @@ class MessageBox extends Component {
} }
} }
identifyMentionKeyword = (keyword, type) => { identifyMentionKeyword = (keyword: any, type: string) => {
this.setState({ this.setState({
showEmojiKeyboard: false, showEmojiKeyboard: false,
trackingType: type trackingType: type,
}); });
this.updateMentions(keyword, type); this.updateMentions(keyword, type);
} }
@ -831,11 +884,11 @@ class MessageBox extends Component {
mentions: [], mentions: [],
trackingType: '', trackingType: '',
commandPreview: [], commandPreview: [],
showCommandPreview: false showCommandPreview: false,
}); });
} }
handleCommands = ({ event }) => { handleCommands = ({ event }: {event: any}) => {
if (handleCommandTyping(event)) { if (handleCommandTyping(event)) {
if (this.focused) { if (this.focused) {
Keyboard.dismiss(); Keyboard.dismiss();
@ -873,16 +926,16 @@ class MessageBox extends Component {
renderContent = () => { renderContent = () => {
const { const {
recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview,
} = this.state; } = this.state;
const { const {
editing, message, replying, replyCancel, user, getCustomEmoji, theme, Message_AudioRecorderEnabled, children, isActionsEnabled, tmid editing, message, replying, replyCancel, user, getCustomEmoji, theme, Message_AudioRecorderEnabled, children, isActionsEnabled, tmid,
} = this.props; } = this.props;
const isAndroidTablet = isTablet && isAndroid ? { const isAndroidTablet = isTablet && isAndroid ? {
multiline: false, multiline: false,
onSubmitEditing: this.submit, onSubmitEditing: this.submit,
returnKeyType: 'send' returnKeyType: 'send',
} : {}; } : {};
const recordAudio = showSend || !Message_AudioRecorderEnabled ? null : ( const recordAudio = showSend || !Message_AudioRecorderEnabled ? null : (
@ -896,12 +949,14 @@ class MessageBox extends Component {
const commandsPreviewAndMentions = !recording ? ( const commandsPreviewAndMentions = !recording ? (
<> <>
<CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} /> <CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} />
{/* @ts-ignore*/}
<Mentions mentions={mentions} trackingType={trackingType} theme={theme} /> <Mentions mentions={mentions} trackingType={trackingType} theme={theme} />
</> </>
) : null; ) : null;
const replyPreview = !recording ? ( const replyPreview = !recording ? (
<ReplyPreview <ReplyPreview
// @ts-ignore
message={message} message={message}
close={replyCancel} close={replyCancel}
username={user.username} username={user.username}
@ -924,9 +979,11 @@ class MessageBox extends Component {
isActionsEnabled={isActionsEnabled} isActionsEnabled={isActionsEnabled}
/> />
<TextInput <TextInput
ref={component => this.component = component} ref={(component) => this.component = component}
// @ts-ignore
style={[styles.textBoxInput, { color: themes[theme].bodyText }]} style={[styles.textBoxInput, { color: themes[theme].bodyText }]}
returnKeyType='default' returnKeyType='default'
// @ts-ignore
keyboardType='twitter' keyboardType='twitter'
blurOnSubmit={false} blurOnSubmit={false}
placeholder={I18n.t('New_Message')} placeholder={I18n.t('New_Message')}
@ -959,7 +1016,7 @@ class MessageBox extends Component {
style={[ style={[
styles.textArea, styles.textArea,
{ backgroundColor: themes[theme].messageboxBackground }, { backgroundColor: themes[theme].messageboxBackground },
!recording && editing && { backgroundColor: themes[theme].chatComponentBackground } !recording && editing && { backgroundColor: themes[theme].chatComponentBackground },
]} ]}
testID='messagebox' testID='messagebox'
> >
@ -977,7 +1034,7 @@ class MessageBox extends Component {
console.count(`${ this.constructor.name }.render calls`); console.count(`${ this.constructor.name }.render calls`);
const { showEmojiKeyboard } = this.state; const { showEmojiKeyboard } = this.state;
const { const {
user, baseUrl, theme, iOSScrollBehavior user, baseUrl, theme, iOSScrollBehavior,
} = this.props; } = this.props;
return ( return (
<MessageboxContext.Provider <MessageboxContext.Provider
@ -985,18 +1042,17 @@ class MessageBox extends Component {
user, user,
baseUrl, baseUrl,
onPressMention: this.onPressMention, onPressMention: this.onPressMention,
onPressCommandPreview: this.onPressCommandPreview onPressCommandPreview: this.onPressCommandPreview,
}} }}
> >
<KeyboardAccessoryView <KeyboardAccessoryView
ref={ref => this.tracking = ref} ref={(ref: any) => this.tracking = ref}
renderContent={this.renderContent} renderContent={this.renderContent}
kbInputRef={this.component} kbInputRef={this.component}
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null} kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
onKeyboardResigned={this.onKeyboardResigned} onKeyboardResigned={this.onKeyboardResigned}
onItemSelected={this.onEmojiSelected} onItemSelected={this.onEmojiSelected}
trackInteractive trackInteractive
// revealKeyboardInteractive
requiresSameParentToManageScrollView requiresSameParentToManageScrollView
addBottomView addBottomView
bottomViewColor={themes[theme].messageboxBackground} bottomViewColor={themes[theme].messageboxBackground}
@ -1007,18 +1063,18 @@ class MessageBox extends Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
baseUrl: state.server.server, baseUrl: state.server.server,
threadsEnabled: state.settings.Threads_enabled, threadsEnabled: state.settings.Threads_enabled,
user: getUserSelector(state), user: getUserSelector(state),
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList, FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize, FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
});
const dispatchToProps = ({
typing: (rid, status) => userTypingAction(rid, status)
}); });
const dispatchToProps = {
typing: (rid: any, status: any) => userTypingAction(rid, status),
};
// @ts-ignore
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox)); export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox));

View File

@ -9,12 +9,12 @@ const SCROLLVIEW_MENTION_HEIGHT = 4 * MENTION_HEIGHT;
export default StyleSheet.create({ export default StyleSheet.create({
composer: { composer: {
flexDirection: 'column', flexDirection: 'column',
borderTopWidth: 1 borderTopWidth: 1,
}, },
textArea: { textArea: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
flexGrow: 0 flexGrow: 0,
}, },
textBoxInput: { textBoxInput: {
textAlignVertical: 'center', textAlignVertical: 'center',
@ -28,48 +28,48 @@ export default StyleSheet.create({
paddingRight: 0, paddingRight: 0,
fontSize: 16, fontSize: 16,
letterSpacing: 0, letterSpacing: 0,
...sharedStyles.textRegular ...sharedStyles.textRegular,
}, },
actionButton: { actionButton: {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
width: 60, width: 60,
height: 48 height: 48,
}, },
mentionList: { mentionList: {
maxHeight: MENTION_HEIGHT * 4 maxHeight: MENTION_HEIGHT * 4,
}, },
mentionItem: { mentionItem: {
height: MENTION_HEIGHT, height: MENTION_HEIGHT,
borderTopWidth: StyleSheet.hairlineWidth, borderTopWidth: StyleSheet.hairlineWidth,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 5 paddingHorizontal: 5,
}, },
mentionItemCustomEmoji: { mentionItemCustomEmoji: {
margin: 8, margin: 8,
width: 30, width: 30,
height: 30 height: 30,
}, },
mentionItemEmoji: { mentionItemEmoji: {
width: 46, width: 46,
height: 36, height: 36,
fontSize: isIOS ? 30 : 25, fontSize: isIOS ? 30 : 25,
...sharedStyles.textAlignCenter ...sharedStyles.textAlignCenter,
}, },
fixedMentionAvatar: { fixedMentionAvatar: {
width: 46, width: 46,
fontSize: 14, fontSize: 14,
...sharedStyles.textBold, ...sharedStyles.textBold,
...sharedStyles.textAlignCenter ...sharedStyles.textAlignCenter,
}, },
mentionText: { mentionText: {
fontSize: 14, fontSize: 14,
...sharedStyles.textRegular ...sharedStyles.textRegular,
}, },
emojiKeyboardContainer: { emojiKeyboardContainer: {
flex: 1, flex: 1,
borderTopWidth: StyleSheet.hairlineWidth borderTopWidth: StyleSheet.hairlineWidth,
}, },
slash: { slash: {
height: 30, height: 30,
@ -77,48 +77,48 @@ export default StyleSheet.create({
padding: 5, padding: 5,
paddingHorizontal: 12, paddingHorizontal: 12,
marginHorizontal: 10, marginHorizontal: 10,
borderRadius: 2 borderRadius: 2,
}, },
commandPreviewImage: { commandPreviewImage: {
justifyContent: 'center', justifyContent: 'center',
margin: 3, margin: 3,
width: 120, width: 120,
height: 80, height: 80,
borderRadius: 4 borderRadius: 4,
}, },
commandPreview: { commandPreview: {
height: 100, height: 100,
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'center',
}, },
avatar: { avatar: {
margin: 8 margin: 8,
}, },
scrollViewMention: { scrollViewMention: {
maxHeight: SCROLLVIEW_MENTION_HEIGHT maxHeight: SCROLLVIEW_MENTION_HEIGHT,
}, },
recordingContent: { recordingContent: {
flexDirection: 'row', flexDirection: 'row',
flex: 1, flex: 1,
justifyContent: 'space-between' justifyContent: 'space-between',
}, },
recordingCancelText: { recordingCancelText: {
fontSize: 16, fontSize: 16,
...sharedStyles.textRegular ...sharedStyles.textRegular,
}, },
buttonsWhitespace: { buttonsWhitespace: {
width: 15 width: 15,
}, },
sendToChannelButton: { sendToChannelButton: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingVertical: 8, paddingVertical: 8,
paddingHorizontal: 18 paddingHorizontal: 18,
}, },
sendToChannelText: { sendToChannelText: {
fontSize: 12, fontSize: 12,
marginLeft: 4, marginLeft: 4,
...sharedStyles.textRegular ...sharedStyles.textRegular,
} },
}); });

View File

@ -1,5 +1,4 @@
import { useImperativeHandle, forwardRef } from 'react'; import { forwardRef, useImperativeHandle } from 'react';
import PropTypes from 'prop-types';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import database from '../lib/database'; import database from '../lib/database';
@ -8,17 +7,17 @@ import { useActionSheet } from './ActionSheet';
import I18n from '../i18n'; import I18n from '../i18n';
import log from '../utils/log'; import log from '../utils/log';
const MessageErrorActions = forwardRef(({ tmid }, ref) => { const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
const { showActionSheet } = useActionSheet(); const { showActionSheet }: any = useActionSheet();
const handleResend = protectedFunction(async(message) => { const handleResend = protectedFunction(async (message: any) => {
await RocketChat.resendMessage(message, tmid); await RocketChat.resendMessage(message, tmid);
}); });
const handleDelete = async(message) => { const handleDelete = async (message: any) => {
try { try {
const db = database.active; const db = database.active;
const deleteBatch = []; const deleteBatch: any = [];
const msgCollection = db.get('messages'); const msgCollection = db.get('messages');
const threadCollection = db.get('threads'); const threadCollection = db.get('threads');
@ -39,10 +38,10 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => {
const msg = await msgCollection.find(tmid); const msg = await msgCollection.find(tmid);
if (msg.tcount <= 1) { if (msg.tcount <= 1) {
deleteBatch.push( deleteBatch.push(
msg.prepareUpdate((m) => { msg.prepareUpdate((m: any) => {
m.tcount = null; m.tcount = null;
m.tlm = null; m.tlm = null;
}) }),
); );
try { try {
@ -54,16 +53,16 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => {
} }
} else { } else {
deleteBatch.push( deleteBatch.push(
msg.prepareUpdate((m) => { msg.prepareUpdate((m: any) => {
m.tcount -= 1; m.tcount -= 1;
}) }),
); );
} }
} catch { } catch {
// Do nothing: message not found // Do nothing: message not found
} }
} }
await db.action(async() => { await db.action(async () => {
await db.batch(...deleteBatch); await db.batch(...deleteBatch);
}); });
} catch (e) { } catch (e) {
@ -71,33 +70,30 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => {
} }
}; };
const showMessageErrorActions = (message) => { const showMessageErrorActions = (message: any) => {
showActionSheet({ showActionSheet({
options: [ options: [
{ {
title: I18n.t('Resend'), title: I18n.t('Resend'),
icon: 'send', icon: 'send',
onPress: () => handleResend(message) onPress: () => handleResend(message),
}, },
{ {
title: I18n.t('Delete'), title: I18n.t('Delete'),
icon: 'delete', icon: 'delete',
danger: true, danger: true,
onPress: () => handleDelete(message) onPress: () => handleDelete(message),
} },
], ],
hasCancel: true hasCancel: true,
}); });
}; };
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
showMessageErrorActions showMessageErrorActions,
})); }));
return null; return null;
}); });
MessageErrorActions.propTypes = {
tmid: PropTypes.string
};
export default MessageErrorActions; export default MessageErrorActions;

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { View, StyleSheet, Text } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import I18n from '../i18n'; import I18n from '../i18n';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
@ -10,34 +9,34 @@ const styles = StyleSheet.create({
container: { container: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginVertical: 24 marginVertical: 24,
}, },
line: { line: {
height: 1, height: 1,
flex: 1 flex: 1,
}, },
text: { text: {
fontSize: 14, fontSize: 14,
marginLeft: 14, marginLeft: 14,
marginRight: 14, marginRight: 14,
...sharedStyles.textMedium ...sharedStyles.textMedium,
} },
}); });
const OrSeparator = React.memo(({ theme }) => { interface IOrSeparator {
theme: string
}
const OrSeparator = React.memo(({ theme }: IOrSeparator) => {
const line = { backgroundColor: themes[theme].borderColor }; const line = { backgroundColor: themes[theme].borderColor };
const text = { color: themes[theme].auxiliaryText }; const text = { color: themes[theme].auxiliaryText };
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={[styles.line, line]} /> <View style={[styles.line, line]} />
<Text style={[styles.text, styles.marginRight, styles.marginLeft, text]}>{I18n.t('OR')}</Text> <Text style={[styles.text, text]}>{I18n.t('OR')}</Text>
<View style={[styles.line, line]} /> <View style={[styles.line, line]} />
</View> </View>
); );
}); });
OrSeparator.propTypes = {
theme: PropTypes.string
};
export default OrSeparator; export default OrSeparator;

View File

@ -1,47 +0,0 @@
import React from 'react';
import { Text } from 'react-native';
import PropTypes from 'prop-types';
import styles from './styles';
import { themes } from '../../../constants/colors';
import Touch from '../../../utils/touch';
import { CustomIcon } from '../../../lib/Icons';
const Button = React.memo(({
text, disabled, theme, onPress, icon
}) => {
const press = () => onPress && onPress(text);
return (
<Touch
style={[styles.buttonView, { backgroundColor: 'transparent' }]}
underlayColor={themes[theme].passcodeButtonActive}
rippleColor={themes[theme].passcodeButtonActive}
enabled={!disabled}
theme={theme}
onPress={press}
>
{
icon
? (
<CustomIcon name={icon} size={36} color={themes[theme].passcodePrimary} />
)
: (
<Text style={[styles.buttonText, { color: themes[theme].passcodePrimary }]}>
{text}
</Text>
)
}
</Touch>
);
});
Button.propTypes = {
text: PropTypes.string,
icon: PropTypes.string,
theme: PropTypes.string,
disabled: PropTypes.bool,
onPress: PropTypes.func
};
export default Button;

View File

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

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import range from 'lodash/range'; import range from 'lodash/range';
import PropTypes from 'prop-types';
import styles from './styles'; import styles from './styles';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
@ -9,10 +8,16 @@ import { themes } from '../../../constants/colors';
const SIZE_EMPTY = 12; const SIZE_EMPTY = 12;
const SIZE_FULL = 16; const SIZE_FULL = 16;
const Dots = React.memo(({ passcode, theme, length }) => ( interface IPasscodeDots {
passcode: string;
theme: string;
length: number;
}
const Dots = React.memo(({ passcode, theme, length }: IPasscodeDots) => (
<View style={styles.dotsContainer}> <View style={styles.dotsContainer}>
{range(length).map((val) => { {range(length).map((val) => {
const lengthSup = (passcode.length >= val + 1); const lengthSup = passcode.length >= val + 1;
const height = lengthSup ? SIZE_FULL : SIZE_EMPTY; const height = lengthSup ? SIZE_FULL : SIZE_EMPTY;
const width = lengthSup ? SIZE_FULL : SIZE_EMPTY; const width = lengthSup ? SIZE_FULL : SIZE_EMPTY;
let backgroundColor = ''; let backgroundColor = '';
@ -33,7 +38,7 @@ const Dots = React.memo(({ passcode, theme, length }) => (
borderRadius, borderRadius,
backgroundColor, backgroundColor,
marginRight, marginRight,
marginLeft marginLeft,
}} }}
/> />
</View> </View>
@ -42,10 +47,4 @@ const Dots = React.memo(({ passcode, theme, length }) => (
</View> </View>
)); ));
Dots.propTypes = {
passcode: PropTypes.string,
theme: PropTypes.string,
length: PropTypes.string
};
export default Dots; export default Dots;

View File

@ -1,13 +1,12 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { Row } from 'react-native-easy-grid'; import { Row } from 'react-native-easy-grid';
import PropTypes from 'prop-types';
import styles from './styles'; import styles from './styles';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
const LockIcon = React.memo(({ theme }) => ( const LockIcon = React.memo(({ theme }: {theme: string}) => (
<Row style={styles.row}> <Row style={styles.row}>
<View style={styles.iconView}> <View style={styles.iconView}>
<CustomIcon name='auth' size={40} color={themes[theme].passcodeLockIcon} /> <CustomIcon name='auth' size={40} color={themes[theme].passcodeLockIcon} />
@ -15,8 +14,4 @@ const LockIcon = React.memo(({ theme }) => (
</Row> </Row>
)); ));
LockIcon.propTypes = {
theme: PropTypes.string
};
export default LockIcon; export default LockIcon;

View File

@ -1,18 +1,28 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Grid } from 'react-native-easy-grid'; import { Grid } from 'react-native-easy-grid';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { resetAttempts } from '../../../utils/localAuthentication'; import { resetAttempts } from '../../../utils/localAuthentication';
import { TYPE } from '../constants'; import { TYPE } from '../constants';
import { getLockedUntil, getDiff } from '../utils'; import { getDiff, getLockedUntil } from '../utils';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import styles from './styles'; import styles from './styles';
import Title from './Title'; import Title from './Title';
import Subtitle from './Subtitle'; import Subtitle from './Subtitle';
import LockIcon from './LockIcon'; import LockIcon from './LockIcon';
const Timer = React.memo(({ time, theme, setStatus }) => { interface IPasscodeTimer {
time: string;
theme: string;
setStatus: Function;
}
interface IPasscodeLocked {
theme: string;
setStatus: Function;
}
const Timer = React.memo(({ time, theme, setStatus }: IPasscodeTimer) => {
const calcTimeLeft = () => { const calcTimeLeft = () => {
const diff = getDiff(time); const diff = getDiff(time);
if (diff > 0) { if (diff > 0) {
@ -20,7 +30,7 @@ const Timer = React.memo(({ time, theme, setStatus }) => {
} }
}; };
const [timeLeft, setTimeLeft] = useState(calcTimeLeft()); const [timeLeft, setTimeLeft] = useState<any>(calcTimeLeft());
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
@ -39,10 +49,10 @@ const Timer = React.memo(({ time, theme, setStatus }) => {
return <Subtitle text={I18n.t('Passcode_app_locked_subtitle', { timeLeft })} theme={theme} />; return <Subtitle text={I18n.t('Passcode_app_locked_subtitle', { timeLeft })} theme={theme} />;
}); });
const Locked = React.memo(({ theme, setStatus }) => { const Locked = React.memo(({ theme, setStatus }: IPasscodeLocked) => {
const [lockedUntil, setLockedUntil] = useState(null); const [lockedUntil, setLockedUntil] = useState<any>(null);
const readItemFromStorage = async() => { const readItemFromStorage = async () => {
const l = await getLockedUntil(); const l = await getLockedUntil();
setLockedUntil(l); setLockedUntil(l);
}; };
@ -52,6 +62,8 @@ const Locked = React.memo(({ theme, setStatus }) => {
}, []); }, []);
return ( return (
// TODO - verify if this 'r' it's correct
// @ts-ignore
<Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]} r> <Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]} r>
<LockIcon theme={theme} /> <LockIcon theme={theme} />
<Title text={I18n.t('Passcode_app_locked_title')} theme={theme} /> <Title text={I18n.t('Passcode_app_locked_title')} theme={theme} />
@ -60,15 +72,4 @@ const Locked = React.memo(({ theme, setStatus }) => {
); );
}); });
Locked.propTypes = {
theme: PropTypes.string,
setStatus: PropTypes.func
};
Timer.propTypes = {
time: PropTypes.string,
theme: PropTypes.string,
setStatus: PropTypes.func
};
export default Locked; export default Locked;

View File

@ -1,12 +1,16 @@
import React from 'react'; import React from 'react';
import { View, Text } from 'react-native'; import { Text, View } from 'react-native';
import { Row } from 'react-native-easy-grid'; import { Row } from 'react-native-easy-grid';
import PropTypes from 'prop-types';
import styles from './styles'; import styles from './styles';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
const Subtitle = React.memo(({ text, theme }) => ( interface IPasscodeSubtitle {
text: string;
theme: string;
}
const Subtitle = React.memo(({ text, theme }: IPasscodeSubtitle) => (
<Row style={styles.row}> <Row style={styles.row}>
<View style={styles.subtitleView}> <View style={styles.subtitleView}>
<Text style={[styles.textSubtitle, { color: themes[theme].passcodeSecondary }]}>{text}</Text> <Text style={[styles.textSubtitle, { color: themes[theme].passcodeSecondary }]}>{text}</Text>
@ -14,9 +18,4 @@ const Subtitle = React.memo(({ text, theme }) => (
</Row> </Row>
)); ));
Subtitle.propTypes = {
text: PropTypes.string,
theme: PropTypes.string
};
export default Subtitle; export default Subtitle;

View File

@ -1,12 +1,16 @@
import React from 'react'; import React from 'react';
import { View, Text } from 'react-native'; import { Text, View } from 'react-native';
import { Row } from 'react-native-easy-grid'; import { Row } from 'react-native-easy-grid';
import PropTypes from 'prop-types';
import styles from './styles'; import styles from './styles';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
const Title = React.memo(({ text, theme }) => ( interface IPasscodeTitle {
text: string;
theme: string;
}
const Title = React.memo(({ text, theme }: IPasscodeTitle) => (
<Row style={styles.row}> <Row style={styles.row}>
<View style={styles.titleView}> <View style={styles.titleView}>
<Text style={[styles.textTitle, { color: themes[theme].passcodePrimary }]}>{text}</Text> <Text style={[styles.textTitle, { color: themes[theme].passcodePrimary }]}>{text}</Text>
@ -14,9 +18,4 @@ const Title = React.memo(({ text, theme }) => (
</Row> </Row>
)); ));
Title.propTypes = {
text: PropTypes.string,
theme: PropTypes.string
};
export default Title; export default Title;

View File

@ -1,9 +1,6 @@
import React, { import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
useState, forwardRef, useImperativeHandle, useRef import { Col, Grid, Row } from 'react-native-easy-grid';
} from 'react';
import { Col, Row, Grid } from 'react-native-easy-grid';
import range from 'lodash/range'; import range from 'lodash/range';
import PropTypes from 'prop-types';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
@ -17,11 +14,23 @@ import LockIcon from './LockIcon';
import Title from './Title'; import Title from './Title';
import Subtitle from './Subtitle'; import Subtitle from './Subtitle';
interface IPasscodeBase {
theme: string;
type: string;
previousPasscode: string;
title: string;
subtitle: string;
showBiometry: string;
onEndProcess: Function;
onError: Function;
onBiometryPress(): void;
}
const Base = forwardRef(({ const Base = forwardRef(({
theme, type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress theme, type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress,
}, ref) => { }: IPasscodeBase, ref) => {
const rootRef = useRef(); const rootRef = useRef<any>();
const dotsRef = useRef(); const dotsRef = useRef<any>();
const [passcode, setPasscode] = useState(''); const [passcode, setPasscode] = useState('');
const clearPasscode = () => setPasscode(''); const clearPasscode = () => setPasscode('');
@ -32,11 +41,11 @@ const Base = forwardRef(({
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
}; };
const animate = (animation, duration = 500) => { const animate = (animation: string, duration = 500) => {
rootRef?.current?.[animation](duration); rootRef?.current?.[animation](duration);
}; };
const onPressNumber = text => setPasscode((p) => { const onPressNumber = (text: string) => setPasscode((p) => {
const currentPasscode = p + text; const currentPasscode = p + text;
if (currentPasscode?.length === PASSCODE_LENGTH) { if (currentPasscode?.length === PASSCODE_LENGTH) {
switch (type) { switch (type) {
@ -69,7 +78,7 @@ const Base = forwardRef(({
}); });
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
wrongPasscode, animate, clearPasscode wrongPasscode, animate, clearPasscode,
})); }));
return ( return (
@ -77,28 +86,28 @@ const Base = forwardRef(({
<Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]}> <Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]}>
<LockIcon theme={theme} /> <LockIcon theme={theme} />
<Title text={title} theme={theme} /> <Title text={title} theme={theme} />
<Subtitle text={subtitle} theme={theme} /> <Subtitle text={subtitle!} theme={theme} />
<Row style={styles.row}> <Row style={styles.row}>
<Animatable.View ref={dotsRef}> <Animatable.View ref={dotsRef}>
<Dots passcode={passcode} theme={theme} length={PASSCODE_LENGTH} /> <Dots passcode={passcode} theme={theme} length={PASSCODE_LENGTH} />
</Animatable.View> </Animatable.View>
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, styles.buttonRow]}>
{range(1, 4).map(i => ( {range(1, 4).map((i: any) => (
<Col key={i} style={styles.colButton}> <Col key={i} style={styles.colButton}>
<Button text={i} theme={theme} onPress={onPressNumber} /> <Button text={i} theme={theme} onPress={onPressNumber} />
</Col> </Col>
))} ))}
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, styles.buttonRow]}>
{range(4, 7).map(i => ( {range(4, 7).map((i: any) => (
<Col key={i} style={styles.colButton}> <Col key={i} style={styles.colButton}>
<Button text={i} theme={theme} onPress={onPressNumber} /> <Button text={i} theme={theme} onPress={onPressNumber} />
</Col> </Col>
))} ))}
</Row> </Row>
<Row style={[styles.row, styles.buttonRow]}> <Row style={[styles.row, styles.buttonRow]}>
{range(7, 10).map(i => ( {range(7, 10).map((i: any) => (
<Col key={i} style={styles.colButton}> <Col key={i} style={styles.colButton}>
<Button text={i} theme={theme} onPress={onPressNumber} /> <Button text={i} theme={theme} onPress={onPressNumber} />
</Col> </Col>
@ -124,16 +133,4 @@ const Base = forwardRef(({
); );
}); });
Base.propTypes = {
theme: PropTypes.string,
type: PropTypes.string,
previousPasscode: PropTypes.string,
title: PropTypes.string,
subtitle: PropTypes.string,
showBiometry: PropTypes.string,
onEndProcess: PropTypes.func,
onError: PropTypes.func,
onBiometryPress: PropTypes.func
};
export default Base; export default Base;

View File

@ -4,22 +4,22 @@ import sharedStyles from '../../../views/Styles';
export default StyleSheet.create({ export default StyleSheet.create({
container: { container: {
flex: 1 flex: 1,
}, },
titleView: { titleView: {
justifyContent: 'center' justifyContent: 'center',
}, },
subtitleView: { subtitleView: {
justifyContent: 'center', justifyContent: 'center',
height: 32 height: 32,
}, },
row: { row: {
flex: 0, flex: 0,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
}, },
buttonRow: { buttonRow: {
height: 102 height: 102,
}, },
colButton: { colButton: {
flex: 0, flex: 0,
@ -27,44 +27,44 @@ export default StyleSheet.create({
marginRight: 12, marginRight: 12,
alignItems: 'center', alignItems: 'center',
width: 78, width: 78,
height: 78 height: 78,
}, },
buttonText: { buttonText: {
fontSize: 28, fontSize: 28,
...sharedStyles.textRegular ...sharedStyles.textRegular,
}, },
buttonView: { buttonView: {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
width: 78, width: 78,
height: 78, height: 78,
borderRadius: 4 borderRadius: 4,
}, },
textTitle: { textTitle: {
fontSize: 22, fontSize: 22,
...sharedStyles.textRegular ...sharedStyles.textRegular,
}, },
textSubtitle: { textSubtitle: {
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium ...sharedStyles.textMedium,
}, },
dotsContainer: { dotsContainer: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
marginTop: 24, marginTop: 24,
marginBottom: 40 marginBottom: 40,
}, },
dotsView: { dotsView: {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
height: 16 height: 16,
}, },
grid: { grid: {
justifyContent: 'center', justifyContent: 'center',
flexDirection: 'column' flexDirection: 'column',
}, },
iconView: { iconView: {
marginVertical: 16 marginVertical: 16,
} },
}); });

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