From 7bf4323dc909d5e3f9c6d9773addb51780e48737 Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 14:55:36 +0500 Subject: [PATCH 01/13] install react-native-keycommand --- ios/Podfile.lock | 12 +++++++++--- package-lock.json | 27 ++++++++++++++++++++++++++- package.json | 1 + 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 16c85d147bb18..77f26a791fe35 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -514,6 +514,8 @@ PODS: - React - react-native-image-picker (5.1.0): - React-Core + - react-native-key-command (1.0.0): + - React-Core - react-native-netinfo (8.3.1): - React-Core - react-native-pdf (6.6.2): @@ -769,6 +771,7 @@ DEPENDENCIES: - react-native-flipper (from `../node_modules/react-native-flipper`) - "react-native-image-manipulator (from `../node_modules/@oguzhnatly/react-native-image-manipulator`)" - react-native-image-picker (from `../node_modules/react-native-image-picker`) + - react-native-key-command (from `../node_modules/react-native-key-command`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-pdf (from `../node_modules/react-native-pdf`) - react-native-performance (from `../node_modules/react-native-performance`) @@ -921,6 +924,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@oguzhnatly/react-native-image-manipulator" react-native-image-picker: :path: "../node_modules/react-native-image-picker" + react-native-key-command: + :path: "../node_modules/react-native-key-command" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-pdf: @@ -1009,7 +1014,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Airship: 19ead2c0bdc791c1b9d6ebb7940aaac99614414e AirshipFrameworkProxy: 037a0ad6491757c45de2c70a6cc47bae5fcfa32b - boost: 57d2868c099736d80fcd648bf211b4431e51a558 + boost: a7c83b31436843459a1961bfd74b96033dc77234 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: ff54429f0110d3c722630a98096ba689c39f6d5f @@ -1052,7 +1057,7 @@ SPEC CHECKSUMS: Permission-LocationWhenInUse: 3ba99e45c852763f730eabecec2870c2382b7bd4 Plaid: 7d340abeadb46c7aa1a91f896c5b22395a31fcf2 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda RCTRequired: e9e7b8b45aa9bedb2fdad71740adf07a7265b9be RCTTypeSafety: 9ae0e9206625e995f0df4d5b9ddc94411929fb30 React: a71c8e1380f07e01de721ccd52bcf9c03e81867d @@ -1074,6 +1079,7 @@ SPEC CHECKSUMS: react-native-flipper: dc5290261fbeeb2faec1bdc57ae6dd8d562e1de4 react-native-image-manipulator: c48f64221cfcd46e9eec53619c4c0374f3328a56 react-native-image-picker: c33d4e79f0a14a2b66e5065e14946ae63749660b + react-native-key-command: 0b3aa7c9f5c052116413e81dce33a3b2153a6c5d react-native-netinfo: 1a6035d3b9780221d407c277ebfb5722ace00658 react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa react-native-performance: 224bd53e6a835fda4353302cf891d088a0af7406 @@ -1123,4 +1129,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: cd132e281e9e3d7e6ec2c99c08e6ec32b37886f8 -COCOAPODS: 1.12.0 +COCOAPODS: 1.11.3 diff --git a/package-lock.json b/package-lock.json index 3961ec1450dac..2f30b5f3d834c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,6 +72,7 @@ "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#6b5ab5110dc3ed554f8eafbc38d7d87c17147972", + "react-native-key-command": "^1.0.0", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", "react-native-onyx": "1.0.38", @@ -138,7 +139,7 @@ "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", - "electron": "^22.3.6", + "electron": "22.3.6", "electron-builder": "23.5.0", "electron-notarize": "^1.2.1", "eslint": "^7.6.0", @@ -34687,6 +34688,21 @@ "integrity": "sha512-jNNpW5byieb7pb/l0HRvmCav4BtzpTzgC+ybT+Wbi2yyroOukveVvnjwWnmoOeuGynsYB4Yt5eGrWZnPnJSwqQ==", "license": "MIT" }, + "node_modules/react-native-key-command": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-native-key-command/-/react-native-key-command-1.0.0.tgz", + "integrity": "sha512-gjtzvJmgssKQ6YWoSiIIM37N/8fxtEUpvrwMZL9YTOg+WSTyJP5C9jIkHiT0KgMmfBylxwoJOCjche9TiNcdDQ==", + "dependencies": { + "events": "^3.3.0", + "underscore": "^1.13.4" + }, + "peerDependencies": { + "react": "^18.1.0", + "react-dom": "18.1.0", + "react-native": "^0.70.4", + "react-native-web": "^0.18.1" + } + }, "node_modules/react-native-localize": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-2.2.6.tgz", @@ -64362,6 +64378,15 @@ "integrity": "sha512-jNNpW5byieb7pb/l0HRvmCav4BtzpTzgC+ybT+Wbi2yyroOukveVvnjwWnmoOeuGynsYB4Yt5eGrWZnPnJSwqQ==", "from": "react-native-image-size@git+https://github.com/Expensify/react-native-image-size#6b5ab5110dc3ed554f8eafbc38d7d87c17147972" }, + "react-native-key-command": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-native-key-command/-/react-native-key-command-1.0.0.tgz", + "integrity": "sha512-gjtzvJmgssKQ6YWoSiIIM37N/8fxtEUpvrwMZL9YTOg+WSTyJP5C9jIkHiT0KgMmfBylxwoJOCjche9TiNcdDQ==", + "requires": { + "events": "^3.3.0", + "underscore": "^1.13.4" + } + }, "react-native-localize": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-2.2.6.tgz", diff --git a/package.json b/package.json index c95800d4a0df2..da9b4b66ec9ee 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#6b5ab5110dc3ed554f8eafbc38d7d87c17147972", + "react-native-key-command": "^1.0.0", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", "react-native-onyx": "1.0.38", From 7dc9c6bd76ea86952b7ee9870d3618117760d04e Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 14:58:25 +0500 Subject: [PATCH 02/13] android library setup --- .../java/com/expensify/chat/MainActivity.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/android/app/src/main/java/com/expensify/chat/MainActivity.java b/android/app/src/main/java/com/expensify/chat/MainActivity.java index bd90ee9abd02c..b4eb483f8de67 100644 --- a/android/app/src/main/java/com/expensify/chat/MainActivity.java +++ b/android/app/src/main/java/com/expensify/chat/MainActivity.java @@ -2,7 +2,9 @@ import android.os.Bundle; import android.content.pm.ActivityInfo; +import android.view.KeyEvent; import com.expensify.chat.bootsplash.BootSplash; +import com.expensify.reactnativekeycommand.KeyCommandModule; import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivityDelegate; import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; @@ -44,4 +46,34 @@ protected void onCreate(Bundle savedInstanceState) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } + + /** + * This method is called when a key down event has occurred. + * Forwards the event to the KeyCommandModule + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // Disabling hardware ESCAPE support which is handled by Android + if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) { + return false; + } + KeyCommandModule.getInstance().onKeyDownEvent(keyCode, event); + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + // Disabling hardware ESCAPE support which is handled by Android + if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) { return false; } + KeyCommandModule.getInstance().onKeyDownEvent(keyCode, event); + return super.onKeyLongPress(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + // Disabling hardware ESCAPE support which is handled by Android + if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) { return false; } + KeyCommandModule.getInstance().onKeyDownEvent(keyCode, event); + return super.onKeyUp(keyCode, event); + } } \ No newline at end of file From 16a939da8687dc07aa9abd83f64e79c2b032cd62 Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 15:00:15 +0500 Subject: [PATCH 03/13] ios library setup --- ios/NewExpensify/AppDelegate.mm | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ios/NewExpensify/AppDelegate.mm b/ios/NewExpensify/AppDelegate.mm index 3238cfeb3c2ac..304de9aade342 100644 --- a/ios/NewExpensify/AppDelegate.mm +++ b/ios/NewExpensify/AppDelegate.mm @@ -8,6 +8,7 @@ #import "RCTBootSplash.h" #import "RCTStartupTimer.h" +#import @interface AppDelegate () @@ -89,4 +90,13 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #endif } +// This methods is needed to support the hardware keyboard shortcuts +- (NSArray *)keyCommands { + return [HardwareShortcuts sharedInstance].keyCommands; +} + +- (void)handleKeyCommand:(UIKeyCommand *)keyCommand { + [[HardwareShortcuts sharedInstance] handleKeyCommand:keyCommand]; +} + @end From 8072f57a737f47c1dd254137de99344ff6e9c416 Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 15:01:18 +0500 Subject: [PATCH 04/13] add jest mocks for keycommand --- __mocks__/react-native-key-command.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 __mocks__/react-native-key-command.js diff --git a/__mocks__/react-native-key-command.js b/__mocks__/react-native-key-command.js new file mode 100644 index 0000000000000..092ab120a1425 --- /dev/null +++ b/__mocks__/react-native-key-command.js @@ -0,0 +1,13 @@ +const registerKeyCommands = () => {}; +const unregisterKeyCommands = () => {}; +const constants = {}; +const eventEmitter = () => {}; +const addListener = () => {}; + +export { + registerKeyCommands, + unregisterKeyCommands, + constants, + eventEmitter, + addListener, +}; From 521f622ccd3389315bf70d70818bb8d18d6f0134 Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 15:03:30 +0500 Subject: [PATCH 05/13] add shortcuts constants --- src/CONST.js | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/CONST.js b/src/CONST.js index ad793fa83c396..09f6e7b62965a 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1,5 +1,6 @@ import lodashGet from 'lodash/get'; import Config from 'react-native-config'; +import * as KeyCommand from 'react-native-key-command'; import * as Url from './libs/Url'; const CLOUDFRONT_DOMAIN = 'cloudfront.net'; @@ -7,10 +8,20 @@ const CLOUDFRONT_URL = `https://d2k5nsl2zxldvw.${CLOUDFRONT_DOMAIN}`; const ACTIVE_EXPENSIFY_URL = Url.addTrailingForwardSlash(lodashGet(Config, 'NEW_EXPENSIFY_URL', 'https://new.expensify.com')); const USE_EXPENSIFY_URL = 'https://use.expensify.com'; const PLATFORM_OS_MACOS = 'Mac OS'; +const PLATFORM_IOS = 'iOS'; const ANDROID_PACKAGE_NAME = 'com.expensify.chat'; const USA_COUNTRY_NAME = 'United States'; const CURRENT_YEAR = new Date().getFullYear(); +const keyModifierControl = lodashGet(KeyCommand, 'constants.keyModifierControl', 'keyModifierControl'); +const keyModifierCommand = lodashGet(KeyCommand, 'constants.keyModifierCommand', 'keyModifierCommand'); +const keyModifierShiftControl = lodashGet(KeyCommand, 'constants.keyModifierShiftControl', 'keyModifierShiftControl'); +const keyModifierShiftCommand = lodashGet(KeyCommand, 'constants.keyModifierShiftCommand', 'keyModifierShiftCommand'); +const keyInputEscape = lodashGet(KeyCommand, 'constants.keyInputEscape', 'keyInputEscape'); +const keyInputEnter = lodashGet(KeyCommand, 'constants.keyInputEnter', 'keyInputEnter'); +const keyInputUpArrow = lodashGet(KeyCommand, 'constants.keyInputUpArrow', 'keyInputUpArrow'); +const keyInputDownArrow = lodashGet(KeyCommand, 'constants.keyInputDownArrow', 'keyInputDownArrow'); + const CONST = { ANDROID_PACKAGE_NAME, ANIMATED_TRANSITION: 300, @@ -224,6 +235,7 @@ const CONST = { CTRL: { DEFAULT: 'control', [PLATFORM_OS_MACOS]: 'meta', + [PLATFORM_IOS]: 'meta', }, SHIFT: { DEFAULT: 'shift', @@ -234,46 +246,91 @@ const CONST = { descriptionKey: 'search', shortcutKey: 'K', modifiers: ['CTRL'], + trigger: { + DEFAULT: {input: 'k', modifierFlags: keyModifierControl}, + [PLATFORM_OS_MACOS]: {input: 'k', modifierFlags: keyModifierCommand}, + [PLATFORM_IOS]: {input: 'k', modifierFlags: keyModifierCommand}, + }, }, NEW_GROUP: { descriptionKey: 'newGroup', shortcutKey: 'K', modifiers: ['CTRL', 'SHIFT'], + trigger: { + DEFAULT: {input: 'k', modifierFlags: keyModifierShiftControl}, + [PLATFORM_OS_MACOS]: {input: 'k', modifierFlags: keyModifierShiftCommand}, + [PLATFORM_IOS]: {input: 'k', modifierFlags: keyModifierShiftCommand}, + }, }, SHORTCUT_MODAL: { descriptionKey: 'openShortcutDialog', shortcutKey: 'I', modifiers: ['CTRL'], + trigger: { + DEFAULT: {input: 'i', modifierFlags: keyModifierControl}, + [PLATFORM_OS_MACOS]: {input: 'i', modifierFlags: keyModifierCommand}, + [PLATFORM_IOS]: {input: 'i', modifierFlags: keyModifierCommand}, + }, }, ESCAPE: { descriptionKey: 'escape', shortcutKey: 'Escape', modifiers: [], + trigger: { + DEFAULT: {input: keyInputEscape}, + [PLATFORM_OS_MACOS]: {input: keyInputEscape}, + [PLATFORM_IOS]: {input: keyInputEscape}, + }, }, ENTER: { descriptionKey: null, shortcutKey: 'Enter', modifiers: [], + trigger: { + DEFAULT: {input: keyInputEnter}, + [PLATFORM_OS_MACOS]: {input: keyInputEnter}, + [PLATFORM_IOS]: {input: keyInputEnter}, + }, }, CTRL_ENTER: { descriptionKey: null, shortcutKey: 'Enter', modifiers: ['CTRL'], + trigger: { + DEFAULT: {input: keyInputEnter, modifierFlags: keyModifierControl}, + [PLATFORM_OS_MACOS]: {input: keyInputEnter, modifierFlags: keyModifierCommand}, + [PLATFORM_IOS]: {input: keyInputEnter, modifierFlags: keyModifierCommand}, + }, }, COPY: { descriptionKey: 'copy', shortcutKey: 'C', modifiers: ['CTRL'], + trigger: { + DEFAULT: {input: 'c', modifierFlags: keyModifierControl}, + [PLATFORM_OS_MACOS]: {input: 'c', modifierFlags: keyModifierCommand}, + [PLATFORM_IOS]: {input: 'c', modifierFlags: keyModifierCommand}, + }, }, ARROW_UP: { descriptionKey: null, shortcutKey: 'ArrowUp', modifiers: [], + trigger: { + DEFAULT: {input: keyInputUpArrow}, + [PLATFORM_OS_MACOS]: {input: keyInputUpArrow}, + [PLATFORM_IOS]: {input: keyInputUpArrow}, + }, }, ARROW_DOWN: { descriptionKey: null, shortcutKey: 'ArrowDown', modifiers: [], + trigger: { + DEFAULT: {input: keyInputDownArrow}, + [PLATFORM_OS_MACOS]: {input: keyInputDownArrow}, + [PLATFORM_IOS]: {input: keyInputDownArrow}, + }, }, TAB: { descriptionKey: null, @@ -783,7 +840,7 @@ const CONST = { WINDOWS: 'Windows', MAC_OS: PLATFORM_OS_MACOS, ANDROID: 'Android', - IOS: 'iOS', + IOS: PLATFORM_IOS, LINUX: 'Linux', NATIVE: 'Native', }, From bb17f08a02b49710e8447df6af50ceebcad575dc Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 15:07:48 +0500 Subject: [PATCH 06/13] move button component to support shortcut validation --- src/components/{Button.js => Button/index.js} | 30 +++++++++---------- .../Button/validateSubmitShortcut/index.js | 19 ++++++++++++ .../validateSubmitShortcut/index.native.js | 17 +++++++++++ 3 files changed, 51 insertions(+), 15 deletions(-) rename src/components/{Button.js => Button/index.js} (93%) create mode 100644 src/components/Button/validateSubmitShortcut/index.js create mode 100644 src/components/Button/validateSubmitShortcut/index.native.js diff --git a/src/components/Button.js b/src/components/Button/index.js similarity index 93% rename from src/components/Button.js rename to src/components/Button/index.js index 817a1c1f9e7d3..3bdb53a115ece 100644 --- a/src/components/Button.js +++ b/src/components/Button/index.js @@ -1,19 +1,20 @@ import React, {Component} from 'react'; import {Pressable, ActivityIndicator, View} from 'react-native'; import PropTypes from 'prop-types'; -import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; -import OpacityView from './OpacityView'; -import Text from './Text'; -import KeyboardShortcut from '../libs/KeyboardShortcut'; -import Icon from './Icon'; -import CONST from '../CONST'; -import * as StyleUtils from '../styles/StyleUtils'; -import HapticFeedback from '../libs/HapticFeedback'; -import withNavigationFallback from './withNavigationFallback'; -import compose from '../libs/compose'; -import * as Expensicons from './Icon/Expensicons'; -import withNavigationFocus from './withNavigationFocus'; +import styles from '../../styles/styles'; +import themeColors from '../../styles/themes/default'; +import OpacityView from '../OpacityView'; +import Text from '../Text'; +import KeyboardShortcut from '../../libs/KeyboardShortcut'; +import Icon from '../Icon'; +import CONST from '../../CONST'; +import * as StyleUtils from '../../styles/StyleUtils'; +import HapticFeedback from '../../libs/HapticFeedback'; +import withNavigationFallback from '../withNavigationFallback'; +import compose from '../../libs/compose'; +import * as Expensicons from '../Icon/Expensicons'; +import withNavigationFocus from '../withNavigationFocus'; +import validateSubmitShortcut from './validateSubmitShortcut'; const propTypes = { /** The text for the button label */ @@ -157,10 +158,9 @@ class Button extends Component { // Setup and attach keypress handler for pressing the button with Enter key this.unsubscribe = KeyboardShortcut.subscribe(shortcutConfig.shortcutKey, (e) => { - if (!this.props.isFocused || this.props.isDisabled || this.props.isLoading || (e && e.target.nodeName === 'TEXTAREA')) { + if (!validateSubmitShortcut(this.props.isFocused, this.props.isDisabled, this.props.isLoading, e)) { return; } - e.preventDefault(); this.props.onPress(); }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true, false, this.props.enterKeyEventListenerPriority, false); } diff --git a/src/components/Button/validateSubmitShortcut/index.js b/src/components/Button/validateSubmitShortcut/index.js new file mode 100644 index 0000000000000..bfe5c79483fa4 --- /dev/null +++ b/src/components/Button/validateSubmitShortcut/index.js @@ -0,0 +1,19 @@ +/** + * Validate if the submit shortcut should be triggered depending on the button state + * + * @param {boolean} isFocused Whether Button is on active screen + * @param {boolean} isDisabled Indicates whether the button should be disabled + * @param {boolean} isLoading Indicates whether the button should be disabled and in the loading state + * @param {Object} event Focused input event + * @returns {boolean} Returns `true` if the shortcut should be triggered + */ +function validateSubmitShortcut(isFocused, isDisabled, isLoading, event) { + if (!isFocused || isDisabled || isLoading || (event && event.target.nodeName === 'TEXTAREA')) { + return false; + } + + event.preventDefault(); + return true; +} + +export default validateSubmitShortcut; diff --git a/src/components/Button/validateSubmitShortcut/index.native.js b/src/components/Button/validateSubmitShortcut/index.native.js new file mode 100644 index 0000000000000..2822fa56d590f --- /dev/null +++ b/src/components/Button/validateSubmitShortcut/index.native.js @@ -0,0 +1,17 @@ +/** + * Validate if the submit shortcut should be triggered depending on the button state + * + * @param {boolean} isFocused Whether Button is on active screen + * @param {boolean} isDisabled Indicates whether the button should be disabled + * @param {boolean} isLoading Indicates whether the button should be disabled and in the loading state + * @returns {boolean} Returns `true` if the shortcut should be triggered + */ +function validateSubmitShortcut(isFocused, isDisabled, isLoading) { + if (!isFocused || isDisabled || isLoading) { + return false; + } + + return true; +} + +export default validateSubmitShortcut; From 36f843b10e93ca45afb670a827cb187d80c592ab Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 15:08:43 +0500 Subject: [PATCH 07/13] add native shortcut support to keyboardshortcutsmodal --- src/components/KeyboardShortcutsModal.js | 28 +++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/KeyboardShortcutsModal.js b/src/components/KeyboardShortcutsModal.js index 81ad2f6428310..0e80652c06a19 100644 --- a/src/components/KeyboardShortcutsModal.js +++ b/src/components/KeyboardShortcutsModal.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {View} from 'react-native'; +import {View, ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import HeaderWithCloseButton from './HeaderWithCloseButton'; @@ -34,18 +34,26 @@ const defaultProps = { class KeyboardShortcutsModal extends React.Component { componentDidMount() { - const shortcutConfig = CONST.KEYBOARD_SHORTCUTS.SHORTCUT_MODAL; - this.unsubscribeShortcutModal = KeyboardShortcut.subscribe(shortcutConfig.shortcutKey, () => { + const openShortcutModalConfig = CONST.KEYBOARD_SHORTCUTS.SHORTCUT_MODAL; + this.unsubscribeShortcutModal = KeyboardShortcut.subscribe(openShortcutModalConfig.shortcutKey, () => { ModalActions.close(); KeyboardShortcutsActions.showKeyboardShortcutModal(); - }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true); + }, openShortcutModalConfig.descriptionKey, openShortcutModalConfig.modifiers, true); + + const closeShortcutModalConfig = CONST.KEYBOARD_SHORTCUTS.ESCAPE; + this.unsubscribeEscapeModal = KeyboardShortcut.subscribe(closeShortcutModalConfig.shortcutKey, () => { + ModalActions.close(); + KeyboardShortcutsActions.hideKeyboardShortcutModal(); + }, closeShortcutModalConfig.descriptionKey, closeShortcutModalConfig.modifiers, true, true); } componentWillUnmount() { - if (!this.unsubscribeShortcutModal) { - return; + if (this.unsubscribeShortcutModal) { + this.unsubscribeShortcutModal(); + } + if (this.unsubscribeEscapeModal) { + this.unsubscribeEscapeModal(); } - this.unsubscribeShortcutModal(); } /** @@ -85,7 +93,7 @@ class KeyboardShortcutsModal extends React.Component { onClose={KeyboardShortcutsActions.hideKeyboardShortcutModal} > - + {this.props.translate('keyboardShortcutModal.subtitle')} @@ -95,7 +103,7 @@ class KeyboardShortcutsModal extends React.Component { })} - + ); } @@ -110,4 +118,4 @@ export default compose( withOnyx({ isShortcutsModalOpen: {key: ONYXKEYS.IS_SHORTCUTS_MODAL_OPEN}, }), -)(KeyboardShortcutsModal); +)(KeyboardShortcutsModal); \ No newline at end of file From cbf030cef97658c683e7afb839e9ceeabac74a09 Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 15:12:28 +0500 Subject: [PATCH 08/13] refactor keyboardshortcut library and add support for react-native-keycommand --- src/components/ScreenWrapper/index.js | 2 +- .../bindHandlerToKeydownEvent/index.js | 54 +++++++++ .../bindHandlerToKeydownEvent/index.native.js | 0 .../KeyboardShortcut/getKeyEventModifiers.js | 27 +++++ src/libs/KeyboardShortcut/index.js | 103 ++++++------------ src/libs/KeyboardShortcut/index.native.js | 12 -- src/pages/home/report/ReportActionCompose.js | 2 +- 7 files changed, 114 insertions(+), 86 deletions(-) create mode 100644 src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js create mode 100644 src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js create mode 100644 src/libs/KeyboardShortcut/getKeyEventModifiers.js delete mode 100644 src/libs/KeyboardShortcut/index.native.js diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index 0975d4db8630d..bf56679c2b906 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -45,7 +45,7 @@ class ScreenWrapper extends React.Component { } Navigation.dismissModal(); - }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true); + }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true, true); this.unsubscribeTransitionEnd = this.props.navigation.addListener('transitionEnd', (event) => { // Prevent firing the prop callback when user is exiting the page. diff --git a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js new file mode 100644 index 0000000000000..338ce921221e4 --- /dev/null +++ b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.js @@ -0,0 +1,54 @@ +import _ from 'underscore'; +import getKeyEventModifiers from '../getKeyEventModifiers'; + +/** + * Checks if an event for that key is configured and if so, runs it. + * @param {Function} getDisplayName + * @param {Object} eventHandlers + * @param {Object} keycommandEvent + * @param {Event} event + * @private + */ +function bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keycommandEvent, event) { + if (!(event instanceof KeyboardEvent)) { + return; + } + + const eventModifiers = getKeyEventModifiers(keycommandEvent); + const displayName = getDisplayName(keycommandEvent.input, eventModifiers); + + // Loop over all the callbacks + _.every(eventHandlers[displayName], (callback) => { + // Early return for excludedNodes + if (_.contains(callback.excludedNodes, event.target.nodeName)) { + return true; + } + + // If configured to do so, prevent input text control to trigger this event + if (!callback.captureOnInputs && ( + event.target.nodeName === 'INPUT' + || event.target.nodeName === 'TEXTAREA' + || event.target.contentEditable === 'true' + )) { + return true; + } + + // Determine if the event should bubble before executing the callback (which may have side-effects) + let shouldBubble = callback.shouldBubble || false; + if (_.isFunction(callback.shouldBubble)) { + shouldBubble = callback.shouldBubble(); + } + + if (_.isFunction(callback.callback)) { + callback.callback(event); + } + if (callback.shouldPreventDefault) { + event.preventDefault(); + } + + // If the event should not bubble, short-circuit the loop + return shouldBubble; + }); +} + +export default bindHandlerToKeydownEvent; diff --git a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/libs/KeyboardShortcut/getKeyEventModifiers.js b/src/libs/KeyboardShortcut/getKeyEventModifiers.js new file mode 100644 index 0000000000000..7865d51a05079 --- /dev/null +++ b/src/libs/KeyboardShortcut/getKeyEventModifiers.js @@ -0,0 +1,27 @@ +import * as KeyCommand from 'react-native-key-command'; +import lodashGet from 'lodash/get'; + +/** + * Gets modifiers from a keyboard event. + * + * @param {Event} event + * @returns {Array} + */ +function getKeyEventModifiers(event) { + if (event.modifierFlags === lodashGet(KeyCommand, 'constants.keyModifierControl', 'keyModifierControl')) { + return ['CONTROL']; + } + if (event.modifierFlags === lodashGet(KeyCommand, 'constants.keyModifierCommand', 'keyModifierCommand')) { + return ['META']; + } + if (event.modifierFlags === lodashGet(KeyCommand, 'constants.keyModifierShiftControl', 'keyModifierShiftControl')) { + return ['CONTROL', 'Shift']; + } + if (event.modifierFlags === lodashGet(KeyCommand, 'constants.keyModifierShiftCommand', 'keyModifierShiftCommand')) { + return ['META', 'Shift']; + } + + return []; +} + +export default getKeyEventModifiers; diff --git a/src/libs/KeyboardShortcut/index.js b/src/libs/KeyboardShortcut/index.js index 39c3a49e06092..9ffd01bbc406b 100644 --- a/src/libs/KeyboardShortcut/index.js +++ b/src/libs/KeyboardShortcut/index.js @@ -1,9 +1,13 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; +import * as KeyCommand from 'react-native-key-command'; +import bindHandlerToKeydownEvent from './bindHandlerToKeydownEvent'; import getOperatingSystem from '../getOperatingSystem'; import CONST from '../../CONST'; +const operatingSystem = getOperatingSystem(); + // Handlers for the various keyboard listeners we set up const eventHandlers = {}; @@ -17,29 +21,6 @@ function getDocumentedShortcuts() { return _.values(documentedShortcuts); } -/** - * Gets modifiers from a keyboard event. - * - * @param {Event} event - * @returns {Array} - */ -function getKeyEventModifiers(event) { - const modifiers = []; - if (event.shiftKey) { - modifiers.push('SHIFT'); - } - if (event.ctrlKey) { - modifiers.push('CONTROL'); - } - if (event.altKey) { - modifiers.push('ALT'); - } - if (event.metaKey) { - modifiers.push('META'); - } - return modifiers; -} - /** * Generates the normalized display name for keyboard shortcuts. * @@ -48,7 +29,23 @@ function getKeyEventModifiers(event) { * @returns {String} */ function getDisplayName(key, modifiers) { - let displayName = [key.toUpperCase()]; + let displayName = (() => { + // Type of key is string and the type of KeyCommand.constants.* is number | string. Use _.isEqual to match different types. + if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputEnter', 'keyInputEnter').toString().toLowerCase())) { + return ['ENTER']; + } + if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputEscape', 'keyInputEscape').toString().toLowerCase())) { + return ['ESCAPE']; + } + if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputUpArrow', 'keyInputUpArrow').toString().toLowerCase())) { + return ['ARROWUP']; + } + if (_.isEqual(key.toLowerCase(), lodashGet(KeyCommand, 'constants.keyInputDownArrow', 'keyInputDownArrow').toString().toLowerCase())) { + return ['ARROWDOWN']; + } + return [key.toUpperCase()]; + })(); + if (_.isString(modifiers)) { displayName.unshift(modifiers); } else if (_.isArray(modifiers)) { @@ -60,56 +57,19 @@ function getDisplayName(key, modifiers) { return displayName.join(' + '); } -/** - * Checks if an event for that key is configured and if so, runs it. - * @param {Event} event - * @private - */ -function bindHandlerToKeydownEvent(event) { - if (!(event instanceof KeyboardEvent)) { +_.each(CONST.KEYBOARD_SHORTCUTS, (shortcut) => { + const shortcutTrigger = lodashGet(shortcut, ['trigger', operatingSystem], lodashGet(shortcut, 'trigger.DEFAULT')); + + // If there is no trigger for the current OS nor a default trigger, then we don't need to do anything + if (!shortcutTrigger) { return; } - const eventModifiers = getKeyEventModifiers(event); - const displayName = getDisplayName(event.key, eventModifiers); - - // Loop over all the callbacks - _.every(eventHandlers[displayName], (callback) => { - // Early return for excludedNodes - if (_.contains(callback.excludedNodes, event.target.nodeName)) { - return true; - } - - // If configured to do so, prevent input text control to trigger this event - if (!callback.captureOnInputs && ( - event.target.nodeName === 'INPUT' - || event.target.nodeName === 'TEXTAREA' - || event.target.contentEditable === 'true' - )) { - return true; - } - - // Determine if the event should bubble before executing the callback (which may have side-effects) - let shouldBubble = callback.shouldBubble || false; - if (_.isFunction(callback.shouldBubble)) { - shouldBubble = callback.shouldBubble(); - } - - if (_.isFunction(callback.callback)) { - callback.callback(event); - } - if (callback.shouldPreventDefault) { - event.preventDefault(); - } - - // If the event should not bubble, short-circuit the loop - return shouldBubble; - }); -} - -// Make sure we don't add multiple listeners -document.removeEventListener('keydown', bindHandlerToKeydownEvent, {capture: true}); -document.addEventListener('keydown', bindHandlerToKeydownEvent, {capture: true}); + KeyCommand.addListener( + shortcutTrigger, + (keycommandEvent, event) => bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keycommandEvent, event), + ); +}); /** * Unsubscribes a keyboard event handler. @@ -129,7 +89,6 @@ function unsubscribe(displayName, callbackID) { * @returns {Array} */ function getPlatformEquivalentForKeys(keys) { - const operatingSystem = getOperatingSystem(); return _.map(keys, (key) => { if (!_.has(CONST.PLATFORM_SPECIFIC_KEYS, key)) { return key; diff --git a/src/libs/KeyboardShortcut/index.native.js b/src/libs/KeyboardShortcut/index.native.js deleted file mode 100644 index 8c97f2daf3432..0000000000000 --- a/src/libs/KeyboardShortcut/index.native.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This is a no-op component for native devices because they wouldn't be able to support keyboard shortcuts like - * a website. - */ -const KeyboardShortcut = { - subscribe() { - return () => {}; - }, - getDocumentedShortcuts() { return []; }, -}; - -export default KeyboardShortcut; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index e664815c95a2e..1071fc63db2f2 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -232,7 +232,7 @@ class ReportActionCompose extends React.Component { } this.updateComment('', true); - }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true); + }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true, true); this.setMaxLines(); this.updateComment(this.comment); From 9e0932ca20c6475742fae857ffb301a0ec9ed8ab Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 15:14:34 +0500 Subject: [PATCH 09/13] remove platform specific code from about page --- .../bindHandlerToKeydownEvent/index.native.js | 33 +++++++++++++++++++ src/pages/settings/AboutPage/AboutPage.js | 10 +++--- .../getPlatformSpecificMenuItems/index.js | 15 --------- .../index.native.js | 1 - src/styles/styles.js | 4 ++- 5 files changed, 42 insertions(+), 21 deletions(-) delete mode 100644 src/pages/settings/AboutPage/getPlatformSpecificMenuItems/index.js delete mode 100644 src/pages/settings/AboutPage/getPlatformSpecificMenuItems/index.native.js diff --git a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js index e69de29bb2d1d..d74becc27abd8 100644 --- a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js +++ b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js @@ -0,0 +1,33 @@ +import _ from 'underscore'; +import getKeyEventModifiers from '../getKeyEventModifiers'; + +/** + * Checks if an event for that key is configured and if so, runs it. + * @param {Function} getDisplayName + * @param {Object} eventHandlers + * @param {Object} keycommandEvent + * @param {Event} event + * @private + */ +function bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keycommandEvent, event) { + const eventModifiers = getKeyEventModifiers(keycommandEvent); + const displayName = getDisplayName(keycommandEvent.input, eventModifiers); + + // Loop over all the callbacks + _.every(eventHandlers[displayName], (callback) => { + // Determine if the event should bubble before executing the callback (which may have side-effects) + let shouldBubble = callback.shouldBubble || false; + if (_.isFunction(callback.shouldBubble)) { + shouldBubble = callback.shouldBubble(); + } + + if (_.isFunction(callback.callback)) { + callback.callback(event); + } + + // If the event should not bubble, short-circuit the loop + return shouldBubble; + }); +} + +export default bindHandlerToKeydownEvent; \ No newline at end of file diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 7c7840fdbbdba..180b82ec258b9 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -17,8 +17,8 @@ import Logo from '../../../../assets/images/new-expensify.svg'; import pkg from '../../../../package.json'; import * as Report from '../../../libs/actions/Report'; import * as Link from '../../../libs/actions/Link'; -import getPlatformSpecificMenuItems from './getPlatformSpecificMenuItems'; import compose from '../../../libs/compose'; +import * as KeyboardShortcuts from '../../../libs/actions/KeyboardShortcuts'; const propTypes = { ...withLocalizePropTypes, @@ -26,8 +26,6 @@ const propTypes = { }; const AboutPage = (props) => { - const platformSpecificMenuItems = getPlatformSpecificMenuItems(props.isSmallScreenWidth); - const menuItems = [ { translationKey: 'initialSettingsPage.aboutPage.appDownloadLinks', @@ -36,7 +34,11 @@ const AboutPage = (props) => { Navigation.navigate(ROUTES.SETTINGS_APP_DOWNLOAD_LINKS); }, }, - ...platformSpecificMenuItems, + { + translationKey: 'initialSettingsPage.aboutPage.viewKeyboardShortcuts', + icon: Expensicons.Keyboard, + action: KeyboardShortcuts.showKeyboardShortcutModal, + }, { translationKey: 'initialSettingsPage.aboutPage.viewTheCode', icon: Expensicons.Eye, diff --git a/src/pages/settings/AboutPage/getPlatformSpecificMenuItems/index.js b/src/pages/settings/AboutPage/getPlatformSpecificMenuItems/index.js deleted file mode 100644 index 52f8ffa2250ff..0000000000000 --- a/src/pages/settings/AboutPage/getPlatformSpecificMenuItems/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import * as KeyboardShortcuts from '../../../../libs/actions/KeyboardShortcuts'; -import * as Expensicons from '../../../../components/Icon/Expensicons'; - -export default (isSmallScreenWidth) => { - if (isSmallScreenWidth) { - return []; - } - return [ - { - translationKey: 'initialSettingsPage.aboutPage.viewKeyboardShortcuts', - icon: Expensicons.Keyboard, - action: KeyboardShortcuts.showKeyboardShortcutModal, - }, - ]; -}; diff --git a/src/pages/settings/AboutPage/getPlatformSpecificMenuItems/index.native.js b/src/pages/settings/AboutPage/getPlatformSpecificMenuItems/index.native.js deleted file mode 100644 index 4ba9480748fc5..0000000000000 --- a/src/pages/settings/AboutPage/getPlatformSpecificMenuItems/index.native.js +++ /dev/null @@ -1 +0,0 @@ -export default () => []; diff --git a/src/styles/styles.js b/src/styles/styles.js index 1219ca71569f3..f541efeeb79d7 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2799,7 +2799,9 @@ const styles = { keyboardShortcutModalContainer: { maxHeight: '100%', - flex: '0 0 auto', + flexShrink: 0, + flexGrow: 0, + flexBasis: 'auto', }, keyboardShortcutTableWrapper: { From 921f61998947f28cc47db257f86df237efd9ef22 Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 15:55:28 +0500 Subject: [PATCH 10/13] fixing linting errors --- src/components/KeyboardShortcutsModal.js | 2 +- .../KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/KeyboardShortcutsModal.js b/src/components/KeyboardShortcutsModal.js index 0e80652c06a19..a5454e280f0d6 100644 --- a/src/components/KeyboardShortcutsModal.js +++ b/src/components/KeyboardShortcutsModal.js @@ -118,4 +118,4 @@ export default compose( withOnyx({ isShortcutsModalOpen: {key: ONYXKEYS.IS_SHORTCUTS_MODAL_OPEN}, }), -)(KeyboardShortcutsModal); \ No newline at end of file +)(KeyboardShortcutsModal); diff --git a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js index d74becc27abd8..de59c819c504a 100644 --- a/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js +++ b/src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.native.js @@ -30,4 +30,4 @@ function bindHandlerToKeydownEvent(getDisplayName, eventHandlers, keycommandEven }); } -export default bindHandlerToKeydownEvent; \ No newline at end of file +export default bindHandlerToKeydownEvent; From 54c94dfa32b2a6cc78f8217238032835c468cc2b Mon Sep 17 00:00:00 2001 From: azimgd Date: Thu, 20 Apr 2023 16:10:16 +0500 Subject: [PATCH 11/13] Merge branch 'keycommand-v3' of github.com:azimgd/expensify-app into keycommand-v3 --- .github/workflows/testBuild.yml | 28 +++++++++- android/app/build.gradle | 4 +- assets/images/new-expensify-adhoc.svg | 50 ++++++++++++++++++ config/electronBuilder.config.js | 8 +-- config/webpack/webpack.common.js | 3 +- desktop/icon-adhoc.png | Bin 0 -> 33796 bytes desktop/main.js | 2 +- fastlane/Fastfile | 4 +- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 +- package.json | 5 +- scripts/build-desktop.sh | 4 +- src/CONST.js | 5 ++ src/components/Badge.js | 7 ++- src/components/EnvironmentBadge.js | 12 +++-- src/components/ExpensifyCashLogo.js | 2 + src/libs/ApiUtils.js | 2 +- src/libs/Environment/Environment.js | 12 +++++ .../getEnvironment/index.native.js | 7 ++- src/libs/OptionsListUtils.js | 3 +- src/libs/ReportUtils.js | 4 +- src/pages/settings/AboutPage/AboutPage.js | 3 +- .../settings/Preferences/PreferencesPage.js | 2 +- src/styles/StyleUtils.js | 6 ++- src/styles/colors.js | 1 + src/styles/styles.js | 8 +++ src/styles/themes/default.js | 2 + 28 files changed, 159 insertions(+), 33 deletions(-) create mode 100644 assets/images/new-expensify-adhoc.svg create mode 100644 desktop/icon-adhoc.png diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index f52453dcdf403..92fde8a4b9c2c 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -71,6 +71,12 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it + run: | + cp .env.staging .env.adhoc + sed -i 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc + echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc + - uses: Expensify/App/.github/actions/composite/setupNode@main - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 @@ -121,6 +127,12 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it + run: | + cp .env.staging .env.adhoc + sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc + echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc + - uses: Expensify/App/.github/actions/composite/setupNode@main - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 @@ -178,6 +190,12 @@ jobs: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} fetch-depth: 0 + - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it + run: | + cp .env.staging .env.adhoc + sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc + echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc + - uses: Expensify/App/.github/actions/composite/setupNode@main - name: Decrypt Developer ID Certificate @@ -192,7 +210,7 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: Build desktop app for testing - run: npm run desktop-build-internal -- --publish always + run: npm run desktop-build-adhoc -- --publish always env: CSC_LINK: ${{ secrets.CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} @@ -214,6 +232,12 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it + run: | + cp .env.staging .env.adhoc + sed -i 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc + echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc + - uses: Expensify/App/.github/actions/composite/setupNode@main - name: Configure AWS Credentials @@ -223,7 +247,7 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: Build web for testing - run: npm run build-staging + run: npm run build-adhoc - name: Build docs run: npm run storybook-build diff --git a/android/app/build.gradle b/android/app/build.gradle index 76e6e8fbba5bc..b2de78ca2f06e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,8 +106,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001030202 - versionName "1.3.2-2" + versionCode 1001030203 + versionName "1.3.2-3" } splits { diff --git a/assets/images/new-expensify-adhoc.svg b/assets/images/new-expensify-adhoc.svg new file mode 100644 index 0000000000000..db2f420c4e0ee --- /dev/null +++ b/assets/images/new-expensify-adhoc.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/electronBuilder.config.js b/config/electronBuilder.config.js index ba018e04b8bc7..46433e4b15743 100644 --- a/config/electronBuilder.config.js +++ b/config/electronBuilder.config.js @@ -6,13 +6,13 @@ const pullRequestNumber = process.env.PULL_REQUEST_NUMBER; const s3Bucket = { production: 'expensify-cash', staging: 'staging-expensify-cash', - internal: 'ad-hoc-expensify-cash', + adhoc: 'ad-hoc-expensify-cash', }; const s3Path = { production: '/', staging: '/', - internal: process.env.PULL_REQUEST_NUMBER + adhoc: process.env.PULL_REQUEST_NUMBER ? `/desktop/${pullRequestNumber}/` : '/', }; @@ -20,10 +20,10 @@ const s3Path = { const macIcon = { production: './desktop/icon.png', staging: './desktop/icon-stg.png', - internal: './desktop/icon-stg.png', + adhoc: './desktop/icon-adhoc.png', }; -const isCorrectElectronEnv = ['production', 'staging', 'internal'].includes( +const isCorrectElectronEnv = ['production', 'staging', 'adhoc'].includes( process.env.ELECTRON_ENV, ); diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js index 50bc7f0f9a81c..59b34693b2676 100644 --- a/config/webpack/webpack.common.js +++ b/config/webpack/webpack.common.js @@ -29,6 +29,7 @@ const envToLogoSuffixMap = { production: '', staging: '-stg', dev: '-dev', + adhoc: '-adhoc', }; function mapEnvToLogoSuffix(envFile) { @@ -120,7 +121,7 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ // React Native JavaScript environment requires the global __DEV__ variable to be accessible. // react-native-render-html uses variable to log exclusively during development. // See https://reactnative.dev/docs/javascript-environment - __DEV__: /staging|prod/.test(envFile) === false, + __DEV__: /staging|prod|adhoc/.test(envFile) === false, }), // This allows us to interactively inspect JS bundle contents diff --git a/desktop/icon-adhoc.png b/desktop/icon-adhoc.png new file mode 100644 index 0000000000000000000000000000000000000000..5812ad6c540403c2e35f73928000de9855b3c646 GIT binary patch literal 33796 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

$y)S`D8a}UGzic^$dy8{-uw0Z0|XB zk{kvE1qSW_BkIHoFBLHpRfKoh?bghS^s}&}sj%(0rPOalALLSrQdhi7d$M=ET7ZA~ z;HPS>V7v3>MBmnXd13v{U-fIO^EPtS#53jz|&jrzRNG`kTt1qEh zgE~(3K2kw~6w!hF%W@rC8GU${xxl0#p`V#N4hZ03tXe|5RE!+j#IHZBSj!m2Mni=) z$c&Em-PR>=ju3Jt_H(z9=I6|y|tXF6kX+|ohN!@`Y45h&c3BL zl|vwz=_|!uME`TITgz~0-%YyX@A$U)nG7`N0rrW<{XnzfLA?v(xPiKFA~Ba@M|`r> zT%Uw&Dt3eJl7!WUlbze~nlh-?na)p? z&(9$09m1x)V)n57XIG@Rd0b$NT&`5v*wACrCqKDBY+U?{MDiy`Qw}dm1;~CteMg9`60a=ODW3OKha{Hz0-I6X1%*_1z>zR&YKwIXpBK_C7c6J9nYT>MgZ+xbP z)Upaq3;ysLM33aQG;l1;f-P3+P_iaN`t`z?YrKWm&!D*307>biUm@|#)-wzT_ks{7 z0ks{#0m8W30!zZnQuPWa_=+|83HwCvtVr~iMupuhA-@jmY@JEE4(iLZcRt$`*@(53 zz7?--3DuSL{Gh=FTE*;-9jhxJOf2EHp75HEHVoa3>&RY!SlzK(2I9z0!9JVpFvGf_ zKURW2u#ko{4j?2Xb42PN8r|aOR+srOcJTD60HyC-Kx(4)p`t;_^BotQ`$vI?hs6J@ zXA2`2QEbchk9xPBrv1yv4kwSqj3m6`iRmzKg~^e7Km3mleSYJ)+sn9o4}B=MjU6m%Nwr-)fpy2X~t2+>knpY z=;A>Ilfz)!j@xLmhm_E15g+J@gWeY!4{oNEI0T5S$H*# zf(R`ucX>BT@e_YNGZ%Y`NRq;$HFmQE2WAA&t#*T}%F@(6y5wNz{0+;e?SogRugmM! zzLie(Sm?};DZH~hC5Si^~jR~&9$olWZC2m%?7s5UoD2LVf&7Xyr6@J zs<@lq<$3elaNqq|dvo8?NUZJw*6i}8)EB-KvUubs>DI?`ib;l5q48;%G@UwH%72sRe~22S>kp$4-qlJg_bJ2rGDP8UMk}3+;6T zr!R>6QqHq0#~=soQHpS!0l>5WPyTe3@CcgGI-Ytyb|7g51kzA_`K(;UD*XQdf7~f^ literal 0 HcmV?d00001 diff --git a/desktop/main.js b/desktop/main.js index 07c51a0bf5f9e..662b2a1361466 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -241,7 +241,7 @@ const mainWindow = (() => { if (__DEV__) { console.debug('CONFIG: ', CONFIG); app.dock.setIcon(`${__dirname}/../icon-dev.png`); - app.setName('New Expensify'); + app.setName('New Expensify Dev'); } app.on('will-finish-launching', () => { diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 577784d3810ae..49f15a3b12c06 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -39,7 +39,7 @@ platform :android do desc "Build app for testing" lane :build_internal do - ENV["ENVFILE"]=".env.staging" + ENV["ENVFILE"]=".env.adhoc" gradle( project_dir: './android', @@ -118,7 +118,7 @@ platform :ios do desc "Build app for testing" lane :build_internal do require 'securerandom' - ENV["ENVFILE"]=".env.staging" + ENV["ENVFILE"]=".env.adhoc" keychain_password = SecureRandom.uuid diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 8cc92a42a4a07..00649cf31d159 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.3.2.2 + 1.3.2.3 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index eb4f6d0326a7e..3308fdd363b10 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.2.2 + 1.3.2.3 diff --git a/package-lock.json b/package-lock.json index 2f30b5f3d834c..d7a3874950d96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.2-2", + "version": "1.3.2-3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.2-2", + "version": "1.3.2-3", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index da9b4b66ec9ee..d4268c82aea63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.2-2", + "version": "1.3.2-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -19,10 +19,11 @@ "web-server": "webpack-dev-server --open --config config/webpack/webpack.dev.js", "build": "webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "build-staging": "webpack --config config/webpack/webpack.common.js --env envFile=.env.staging", + "build-adhoc": "webpack --config config/webpack/webpack.common.js --env envFile=.env.adhoc", "desktop": "scripts/set-pusher-suffix.sh && node desktop/start.js", "desktop-build": "scripts/build-desktop.sh production", "desktop-build-staging": "scripts/build-desktop.sh staging", - "desktop-build-internal": "scripts/build-desktop.sh internal", + "desktop-build-adhoc": "scripts/build-desktop.sh adhoc", "ios-build": "fastlane ios build", "android-build": "fastlane android build", "android-build-e2e": "bundle exec fastlane android build_e2e", diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh index ce8737ee1b18d..c67c37a527a28 100755 --- a/scripts/build-desktop.sh +++ b/scripts/build-desktop.sh @@ -5,8 +5,8 @@ export ELECTRON_ENV=${1:-development} if [[ "$ELECTRON_ENV" == "staging" ]]; then ENV_FILE=".env.staging" -elif [[ "$ELECTRON_ENV" == "internal" ]]; then - ENV_FILE=".env.staging" +elif [[ "$ELECTRON_ENV" == "adhoc" ]]; then + ENV_FILE=".env.adhoc" elif [[ "$ELECTRON_ENV" == "production" ]]; then ENV_FILE=".env.production" else diff --git a/src/CONST.js b/src/CONST.js index 09f6e7b62965a..212fe0855d246 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -12,6 +12,7 @@ const PLATFORM_IOS = 'iOS'; const ANDROID_PACKAGE_NAME = 'com.expensify.chat'; const USA_COUNTRY_NAME = 'United States'; const CURRENT_YEAR = new Date().getFullYear(); +const PULL_REQUEST_NUMBER = lodashGet(Config, 'PULL_REQUEST_NUMBER', ''); const keyModifierControl = lodashGet(KeyCommand, 'constants.keyModifierControl', 'keyModifierControl'); const keyModifierCommand = lodashGet(KeyCommand, 'constants.keyModifierCommand', 'keyModifierCommand'); @@ -66,6 +67,8 @@ const CONST = { RESERVED_FIRST_NAMES: ['Expensify', 'Concierge'], }, + PULL_REQUEST_NUMBER, + CALENDAR_PICKER: { // Numbers were arbitrarily picked. MIN_YEAR: CURRENT_YEAR - 100, @@ -358,6 +361,7 @@ const CONST = { PDF_VIEWER_URL: '/pdf/web/viewer.html', CLOUDFRONT_DOMAIN_REGEX: /^https:\/\/\w+\.cloudfront\.net/i, EXPENSIFY_ICON_URL: `${CLOUDFRONT_URL}/images/favicon-2019.png`, + CONCIERGE_ICON_URL: `${CLOUDFRONT_URL}/images/icons/concierge_2022.png`, UPWORK_URL: 'https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22', GITHUB_URL: 'https://github.com/Expensify/App', TERMS_URL: `${USE_EXPENSIFY_URL}/terms`, @@ -750,6 +754,7 @@ const CONST = { DEV: 'development', STAGING: 'staging', PRODUCTION: 'production', + ADHOC: 'adhoc', }, // Used to delay the initial fetching of reportActions when the app first inits or reconnects (e.g. returning diff --git a/src/components/Badge.js b/src/components/Badge.js index d83c196e31e8c..b1eafb227bc41 100644 --- a/src/components/Badge.js +++ b/src/components/Badge.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import Text from './Text'; +import CONST from '../CONST'; const propTypes = { /** Is Success type */ @@ -18,6 +19,9 @@ const propTypes = { /** Text to display in the Badge */ text: PropTypes.string.isRequired, + /** Text to display in the Badge */ + environment: PropTypes.string, + /** Styles for Badge */ // eslint-disable-next-line react/forbid-prop-types badgeStyles: PropTypes.arrayOf(PropTypes.object), @@ -32,6 +36,7 @@ const defaultProps = { pressable: false, badgeStyles: [], onPress: undefined, + environment: CONST.ENVIRONMENT.DEV, }; const Badge = (props) => { @@ -40,7 +45,7 @@ const Badge = (props) => { const wrapperStyles = ({pressed}) => ([ styles.badge, styles.ml2, - StyleUtils.getBadgeColorStyle(props.success, props.error, pressed), + StyleUtils.getBadgeColorStyle(props.success, props.error, pressed, props.environment === CONST.ENVIRONMENT.ADHOC), ...props.badgeStyles, ]); diff --git a/src/components/EnvironmentBadge.js b/src/components/EnvironmentBadge.js index 4365bfab1a115..9e85b3ef2d971 100644 --- a/src/components/EnvironmentBadge.js +++ b/src/components/EnvironmentBadge.js @@ -3,11 +3,14 @@ import CONST from '../CONST'; import withEnvironment, {environmentPropTypes} from './withEnvironment'; import Badge from './Badge'; import styles from '../styles/styles'; +import * as Environment from '../libs/Environment/Environment'; +import pkg from '../../package.json'; const ENVIRONMENT_SHORT_FORM = { [CONST.ENVIRONMENT.DEV]: 'DEV', [CONST.ENVIRONMENT.STAGING]: 'STG', [CONST.ENVIRONMENT.PRODUCTION]: 'PROD', + [CONST.ENVIRONMENT.ADHOC]: 'ADHOC', }; const EnvironmentBadge = (props) => { @@ -16,12 +19,15 @@ const EnvironmentBadge = (props) => { return null; } + const text = Environment.isInternalTestBuild() ? `v${pkg.version} PR:${CONST.PULL_REQUEST_NUMBER}` : ENVIRONMENT_SHORT_FORM[props.environment]; + return ( ); }; diff --git a/src/components/ExpensifyCashLogo.js b/src/components/ExpensifyCashLogo.js index 06a687460091f..0c709581dacd6 100644 --- a/src/components/ExpensifyCashLogo.js +++ b/src/components/ExpensifyCashLogo.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import ProductionLogo from '../../assets/images/new-expensify.svg'; import DevLogo from '../../assets/images/new-expensify-dev.svg'; import StagingLogo from '../../assets/images/new-expensify-stg.svg'; +import AdhocLogo from '../../assets/images/new-expensify-adhoc.svg'; import CONST from '../CONST'; import withEnvironment, {environmentPropTypes} from './withEnvironment'; @@ -20,6 +21,7 @@ const logoComponents = { [CONST.ENVIRONMENT.DEV]: DevLogo, [CONST.ENVIRONMENT.STAGING]: StagingLogo, [CONST.ENVIRONMENT.PRODUCTION]: ProductionLogo, + [CONST.ENVIRONMENT.ADHOC]: AdhocLogo, }; const ExpensifyCashLogo = (props) => { diff --git a/src/libs/ApiUtils.js b/src/libs/ApiUtils.js index 08533c83b076e..7e3e5b9cd5b96 100644 --- a/src/libs/ApiUtils.js +++ b/src/libs/ApiUtils.js @@ -24,7 +24,7 @@ Environment.getEnvironment() return; } - const defaultToggleState = ENV_NAME === CONST.ENVIRONMENT.STAGING; + const defaultToggleState = ENV_NAME === CONST.ENVIRONMENT.STAGING || ENV_NAME === CONST.ENVIRONMENT.ADHOC; shouldUseStagingServer = lodashGet(val, 'shouldUseStagingServer', defaultToggleState); }, }); diff --git a/src/libs/Environment/Environment.js b/src/libs/Environment/Environment.js index bdba9f20d12dd..7bce69833d2e5 100644 --- a/src/libs/Environment/Environment.js +++ b/src/libs/Environment/Environment.js @@ -8,12 +8,14 @@ const ENVIRONMENT_URLS = { [CONST.ENVIRONMENT.DEV]: CONST.DEV_NEW_EXPENSIFY_URL + CONFIG.DEV_PORT, [CONST.ENVIRONMENT.STAGING]: CONST.STAGING_NEW_EXPENSIFY_URL, [CONST.ENVIRONMENT.PRODUCTION]: CONST.NEW_EXPENSIFY_URL, + [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_NEW_EXPENSIFY_URL, }; const OLDDOT_ENVIRONMENT_URLS = { [CONST.ENVIRONMENT.DEV]: CONST.INTERNAL_DEV_EXPENSIFY_URL, [CONST.ENVIRONMENT.STAGING]: CONST.STAGING_EXPENSIFY_URL, [CONST.ENVIRONMENT.PRODUCTION]: CONST.EXPENSIFY_URL, + [CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_EXPENSIFY_URL, }; /** @@ -25,6 +27,15 @@ function isDevelopment() { return lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.DEV; } +/** + * Are we running an internal test build? + * + * @return {boolean} + */ +function isInternalTestBuild() { + return lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC && lodashGet(Config, 'PULL_REQUEST_NUMBER', ''); +} + /** * Get the URL based on the environment we are in * @@ -49,6 +60,7 @@ function getOldDotEnvironmentURL() { export { getEnvironment, + isInternalTestBuild, isDevelopment, getEnvironmentURL, getOldDotEnvironmentURL, diff --git a/src/libs/Environment/getEnvironment/index.native.js b/src/libs/Environment/getEnvironment/index.native.js index 89547e0ee54d5..5b2d065933b16 100644 --- a/src/libs/Environment/getEnvironment/index.native.js +++ b/src/libs/Environment/getEnvironment/index.native.js @@ -22,7 +22,12 @@ function getEnvironment() { return resolve(environment); } - // If we haven't set the environment yet and we aren't on dev, check to see if this is a beta build + if (lodashGet(Config, 'ENVIRONMENT', CONST.ENVIRONMENT.DEV) === CONST.ENVIRONMENT.ADHOC) { + environment = CONST.ENVIRONMENT.ADHOC; + return resolve(environment); + } + + // If we haven't set the environment yet and we aren't on dev/adhoc, check to see if this is a beta build betaChecker.isBetaBuild() .then((isBeta) => { environment = isBeta ? CONST.ENVIRONMENT.STAGING : CONST.ENVIRONMENT.PRODUCTION; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 37103054493c1..e5a5d22bec6ea 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -8,7 +8,6 @@ import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import * as ReportUtils from './ReportUtils'; import * as Localize from './Localize'; -import * as Expensicons from '../components/Icon/Expensicons'; import Permissions from './Permissions'; import * as CollectionUtils from './CollectionUtils'; import Navigation from './Navigation/Navigation'; @@ -169,7 +168,7 @@ function getPersonalDetailsForLogins(logins, personalDetails) { } if (login === CONST.EMAIL.CONCIERGE) { - personalDetail.avatar = Expensicons.ConciergeAvatar; + personalDetail.avatar = CONST.CONCIERGE_ICON_URL; } personalDetailsForLogins[login] = personalDetail; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 677e842f68554..93844f5ba8862 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -536,7 +536,7 @@ function getDefaultWorkspaceAvatar(workspaceName) { */ function getOldDotDefaultAvatar(login = '') { if (login === CONST.EMAIL.CONCIERGE) { - return Expensicons.ConciergeAvatar; + return CONST.CONCIERGE_ICON_URL; } // There are 8 possible old dot default avatars, so we choose which one this user has based @@ -643,7 +643,7 @@ function getIcons(report, personalDetails, policies, defaultIcon = null) { return [result]; } if (isConciergeChatReport(report)) { - result.source = Expensicons.ConciergeAvatar; + result.source = CONST.CONCIERGE_ICON_URL; return [result]; } if (isArchivedRoom(report)) { diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 180b82ec258b9..19771c097d525 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -19,6 +19,7 @@ import * as Report from '../../../libs/actions/Report'; import * as Link from '../../../libs/actions/Link'; import compose from '../../../libs/compose'; import * as KeyboardShortcuts from '../../../libs/actions/KeyboardShortcuts'; +import * as Environment from '../../../libs/Environment/Environment'; const propTypes = { ...withLocalizePropTypes, @@ -95,7 +96,7 @@ const AboutPage = (props) => { ]} > v - {pkg.version} + {Environment.isInternalTestBuild() ? `${pkg.version} PR:${CONST.PULL_REQUEST_NUMBER}` : pkg.version} {props.translate('initialSettingsPage.aboutPage.description')} diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js index 953b6de8dd66a..3594e37d6deb2 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -47,7 +47,7 @@ const PreferencesPage = (props) => { const languages = props.translate('languagePage.languages'); // Enable additional test features in the staging or dev environments - const shouldShowTestToolMenu = _.contains([CONST.ENVIRONMENT.STAGING, CONST.ENVIRONMENT.DEV], props.environment); + const shouldShowTestToolMenu = _.contains([CONST.ENVIRONMENT.STAGING, CONST.ENVIRONMENT.ADHOC, CONST.ENVIRONMENT.DEV], props.environment); return ( diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index f78e56839e5bb..0381ad8b4a26e 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -343,10 +343,14 @@ function getBackgroundColorWithOpacityStyle(backgroundColor, opacity) { * @param {Boolean} success * @param {Boolean} error * @param {boolean} [isPressed=false] + * @param {boolean} [isAdHoc=false] * @return {Object} */ -function getBadgeColorStyle(success, error, isPressed = false) { +function getBadgeColorStyle(success, error, isPressed = false, isAdHoc = false) { if (success) { + if (isAdHoc) { + return isPressed ? styles.badgeAdHocSuccessPressed : styles.badgeAdHocSuccess; + } return isPressed ? styles.badgeSuccessPressed : styles.badgeSuccess; } if (error) { diff --git a/src/styles/colors.js b/src/styles/colors.js index a4c203194f578..592715e66ab1b 100644 --- a/src/styles/colors.js +++ b/src/styles/colors.js @@ -59,6 +59,7 @@ export default { pink200: '#FBCCFF', pink400: '#F68DFE', + pink600: '#CF4CD9', pink700: '#712A76', pink800: '#49225B', diff --git a/src/styles/styles.js b/src/styles/styles.js index f541efeeb79d7..7dd7ead55cae6 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -671,6 +671,14 @@ const styles = { backgroundColor: themeColors.successHover, }, + badgeAdHocSuccess: { + backgroundColor: themeColors.badgeAdHoc, + }, + + badgeAdHocSuccessPressed: { + backgroundColor: themeColors.badgeAdHocHover, + }, + badgeDanger: { backgroundColor: themeColors.danger, }, diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index 67a30c0c0eb69..c35b0d53eb26c 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -67,6 +67,8 @@ const darkTheme = { imageCropBackgroundColor: colors.greenIcons, fallbackIconColor: colors.green700, reactionActive: '#003C73', + badgeAdHoc: colors.pink600, + badgeAdHocHover: colors.pink700, }; const oldTheme = { From abb95b025aaf3e82eba17b3c30d2e9a535215c29 Mon Sep 17 00:00:00 2001 From: azimgd Date: Fri, 21 Apr 2023 23:27:20 +0500 Subject: [PATCH 12/13] change CMD+I to CMD+J which was ignored by iOS --- src/CONST.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 212fe0855d246..b387607b4a3bc 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -267,12 +267,12 @@ const CONST = { }, SHORTCUT_MODAL: { descriptionKey: 'openShortcutDialog', - shortcutKey: 'I', + shortcutKey: 'J', modifiers: ['CTRL'], trigger: { - DEFAULT: {input: 'i', modifierFlags: keyModifierControl}, - [PLATFORM_OS_MACOS]: {input: 'i', modifierFlags: keyModifierCommand}, - [PLATFORM_IOS]: {input: 'i', modifierFlags: keyModifierCommand}, + DEFAULT: {input: 'j', modifierFlags: keyModifierControl}, + [PLATFORM_OS_MACOS]: {input: 'j', modifierFlags: keyModifierCommand}, + [PLATFORM_IOS]: {input: 'j', modifierFlags: keyModifierCommand}, }, }, ESCAPE: { From e63cf1a06a5758c31e609369eafd2b2433dcdc34 Mon Sep 17 00:00:00 2001 From: azimgd Date: Mon, 24 Apr 2023 18:59:21 +0500 Subject: [PATCH 13/13] revert automatic changes to project.pbxproj --- ios/NewExpensify.xcodeproj/project.pbxproj | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index f34f8959aab08..a93599a492d07 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 0C7C65547D7346EB923BE808 /* ExpensifyMono-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = E704648954784DDFBAADF568 /* ExpensifyMono-Regular.otf */; }; 0CDA8E34287DD650004ECBEC /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0CDA8E33287DD650004ECBEC /* AppDelegate.mm */; }; 0CDA8E35287DD650004ECBEC /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0CDA8E33287DD650004ECBEC /* AppDelegate.mm */; }; 0CDA8E37287DD6A0004ECBEC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CDA8E36287DD6A0004ECBEC /* Images.xcassets */; }; @@ -15,12 +14,8 @@ 0F5BE0CE252686330097D869 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0F5BE0CD252686320097D869 /* GoogleService-Info.plist */; }; 0F5E5350263B73FD004CA14F /* EnvironmentChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */; }; 0F5E5351263B73FD004CA14F /* EnvironmentChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */; }; - 1246A3EF20E54E7A9494C8B9 /* ExpensifyNeue-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = F4F8A052A22040339996324B /* ExpensifyNeue-Regular.otf */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 18D050E0262400AF000D658B /* BridgingFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D050DF262400AF000D658B /* BridgingFile.swift */; }; - 26AF3C3540374A9FACB6C19E /* ExpensifyMono-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DCF33E34FFEC48128CDD41D4 /* ExpensifyMono-Bold.otf */; }; - 2A9F8CDA983746B0B9204209 /* ExpensifyNeue-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 52796131E6554494B2DDB056 /* ExpensifyNeue-Bold.otf */; }; - 30581EA8AAFD4FCE88C5D191 /* ExpensifyNeue-Italic.otf in Resources */ = {isa = PBXBuildFile; fileRef = BF6A4C5167244B9FB8E4D4E3 /* ExpensifyNeue-Italic.otf */; }; 374FB8D728A133FE000D84EF /* OriginImageRequestHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 374FB8D628A133FE000D84EF /* OriginImageRequestHandler.mm */; }; 7041848526A8E47D00E09F4D /* RCTStartupTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7041848426A8E47D00E09F4D /* RCTStartupTimer.m */; }; 7041848626A8E47D00E09F4D /* RCTStartupTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7041848426A8E47D00E09F4D /* RCTStartupTimer.m */; }; @@ -31,9 +26,14 @@ DD79042B2792E76D004484B4 /* RCTBootSplash.m in Sources */ = {isa = PBXBuildFile; fileRef = DD79042A2792E76D004484B4 /* RCTBootSplash.m */; }; E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; E9DF872D2525201700607FDC /* AirshipConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = E9DF872C2525201700607FDC /* AirshipConfig.plist */; }; - ED222ED90E074A5481A854FA /* ExpensifyNeue-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */; }; F0C450EA2705020500FD2970 /* colors.json in Resources */ = {isa = PBXBuildFile; fileRef = F0C450E92705020500FD2970 /* colors.json */; }; FF941A8D48F849269AB85C9A /* ExpensifyNewKansas-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = 44BF435285B94E5B95F90994 /* ExpensifyNewKansas-Medium.otf */; }; + 26AF3C3540374A9FACB6C19E /* ExpensifyMono-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DCF33E34FFEC48128CDD41D4 /* ExpensifyMono-Bold.otf */; }; + 0C7C65547D7346EB923BE808 /* ExpensifyMono-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = E704648954784DDFBAADF568 /* ExpensifyMono-Regular.otf */; }; + 2A9F8CDA983746B0B9204209 /* ExpensifyNeue-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 52796131E6554494B2DDB056 /* ExpensifyNeue-Bold.otf */; }; + ED222ED90E074A5481A854FA /* ExpensifyNeue-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */; }; + 30581EA8AAFD4FCE88C5D191 /* ExpensifyNeue-Italic.otf in Resources */ = {isa = PBXBuildFile; fileRef = BF6A4C5167244B9FB8E4D4E3 /* ExpensifyNeue-Italic.otf */; }; + 1246A3EF20E54E7A9494C8B9 /* ExpensifyNeue-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = F4F8A052A22040339996324B /* ExpensifyNeue-Regular.otf */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -66,25 +66,25 @@ 37F6DD6E91B4C55BD8DDC895 /* Pods-NewExpensify.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.debug.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.debug.xcconfig"; sourceTree = ""; }; 391B5D1DB6CFBAC16FD11DC4 /* Pods-NewExpensify.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.release.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.release.xcconfig"; sourceTree = ""; }; 44BF435285B94E5B95F90994 /* ExpensifyNewKansas-Medium.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNewKansas-Medium.otf"; path = "../assets/fonts/native/ExpensifyNewKansas-Medium.otf"; sourceTree = ""; }; - 52796131E6554494B2DDB056 /* ExpensifyNeue-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Bold.otf"; path = "../assets/fonts/native/ExpensifyNeue-Bold.otf"; sourceTree = ""; }; 7041848326A8E40900E09F4D /* RCTStartupTimer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RCTStartupTimer.h; path = NewExpensify/RCTStartupTimer.h; sourceTree = ""; }; 7041848426A8E47D00E09F4D /* RCTStartupTimer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RCTStartupTimer.m; path = NewExpensify/RCTStartupTimer.m; sourceTree = ""; }; 70CF6E81262E297300711ADC /* BootSplash.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = BootSplash.storyboard; path = NewExpensify/BootSplash.storyboard; sourceTree = ""; }; - 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-BoldItalic.otf"; path = "../assets/fonts/native/ExpensifyNeue-BoldItalic.otf"; sourceTree = ""; }; B37C757CE02B734BFED38097 /* Pods-NewExpensify-NewExpensifyTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.debug.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.debug.xcconfig"; sourceTree = ""; }; - BF6A4C5167244B9FB8E4D4E3 /* ExpensifyNeue-Italic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Italic.otf"; path = "../assets/fonts/native/ExpensifyNeue-Italic.otf"; sourceTree = ""; }; CA3A3642AEED7CF2D4CD3716 /* Pods-NewExpensify-NewExpensifyTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.release.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.release.xcconfig"; sourceTree = ""; }; D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNewKansas-MediumItalic.otf"; path = "../assets/fonts/native/ExpensifyNewKansas-MediumItalic.otf"; sourceTree = ""; }; - DCF33E34FFEC48128CDD41D4 /* ExpensifyMono-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-Bold.otf"; path = "../assets/fonts/native/ExpensifyMono-Bold.otf"; sourceTree = ""; }; DD7904292792E76D004484B4 /* RCTBootSplash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTBootSplash.h; path = NewExpensify/RCTBootSplash.h; sourceTree = ""; }; DD79042A2792E76D004484B4 /* RCTBootSplash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RCTBootSplash.m; path = NewExpensify/RCTBootSplash.m; sourceTree = ""; }; - E704648954784DDFBAADF568 /* ExpensifyMono-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-Regular.otf"; path = "../assets/fonts/native/ExpensifyMono-Regular.otf"; sourceTree = ""; }; E9DF872C2525201700607FDC /* AirshipConfig.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = AirshipConfig.plist; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; F0C450E92705020500FD2970 /* colors.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = colors.json; path = ../colors.json; sourceTree = ""; }; - F4F8A052A22040339996324B /* ExpensifyNeue-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Regular.otf"; path = "../assets/fonts/native/ExpensifyNeue-Regular.otf"; sourceTree = ""; }; F679A86058F8C4B331D239C3 /* libPods-NewExpensify-NewExpensifyTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NewExpensify-NewExpensifyTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + DCF33E34FFEC48128CDD41D4 /* ExpensifyMono-Bold.otf */ = {isa = PBXFileReference; name = "ExpensifyMono-Bold.otf"; path = "../assets/fonts/native/ExpensifyMono-Bold.otf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + E704648954784DDFBAADF568 /* ExpensifyMono-Regular.otf */ = {isa = PBXFileReference; name = "ExpensifyMono-Regular.otf"; path = "../assets/fonts/native/ExpensifyMono-Regular.otf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 52796131E6554494B2DDB056 /* ExpensifyNeue-Bold.otf */ = {isa = PBXFileReference; name = "ExpensifyNeue-Bold.otf"; path = "../assets/fonts/native/ExpensifyNeue-Bold.otf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */ = {isa = PBXFileReference; name = "ExpensifyNeue-BoldItalic.otf"; path = "../assets/fonts/native/ExpensifyNeue-BoldItalic.otf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + BF6A4C5167244B9FB8E4D4E3 /* ExpensifyNeue-Italic.otf */ = {isa = PBXFileReference; name = "ExpensifyNeue-Italic.otf"; path = "../assets/fonts/native/ExpensifyNeue-Italic.otf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + F4F8A052A22040339996324B /* ExpensifyNeue-Regular.otf */ = {isa = PBXFileReference; name = "ExpensifyNeue-Regular.otf"; path = "../assets/fonts/native/ExpensifyNeue-Regular.otf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */