diff --git a/android/app/build.gradle b/android/app/build.gradle index 363d01f59456..9febb141ca00 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -152,8 +152,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001018508 - versionName "1.1.85-8" + versionCode 1001018604 + versionName "1.1.86-4" } splits { abi { diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index 5a06599d8936..2cde9aff521f 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -37,6 +37,8 @@ Payment for your contributions will be made no less than 7 days after the pull r New contributors are limited to working on one job at a time, however experienced contributors may work on numerous jobs simultaneously. +Please be aware that compensation for any support in solving an issue is provided **entirely at Expensify’s discretion**. Personal time or resources applied towards investigating a proposal **will not guarantee compensation**. Compensation is only guaranteed to those who **[propose a solution and get hired for that job](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#propose-a-solution-for-the-job)**. We understand there may be cases where a selected proposal may take inspiration from a previous proposal. Unfortunately, it’s not possible for us to evaluate every individual case and we have no process that can efficiently do so. Issues with higher rewards come with higher risk factors so try to keep things civil and make the best proposal you can. Once again, **any information provided may not necessarily lead to you getting hired for that issue or compensated in any way.** + ## Finding Jobs There are two ways you can find a job that you can contribute to: diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 9bbbbf689168..cb5d5aa80d9e 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.85 + 1.1.86 CFBundleSignature ???? CFBundleURLTypes @@ -30,7 +30,7 @@ CFBundleVersion - 1.1.85.8 + 1.1.86.4 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index d75ea9a05b32..eb01d7bf5b73 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.1.85 + 1.1.86 CFBundleSignature ???? CFBundleVersion - 1.1.85.8 + 1.1.86.4 diff --git a/package-lock.json b/package-lock.json index 8d82e536def4..eb3249d9fce0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.1.85-8", + "version": "1.1.86-4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 969eb7a54a27..66304d3c7f8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.1.85-8", + "version": "1.1.86-4", "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.", diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index 62598e658f0f..c0f926430b0c 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -148,12 +148,12 @@ class AddPlaidBankAccount extends React.Component { )} - {this.props.plaidData.error && ( + {Boolean(this.props.plaidData.error) && ( {this.props.plaidData.error} )} - {token && ( + {Boolean(token) && ( { @@ -217,6 +217,7 @@ export default compose( }, plaidLinkToken: { key: ONYXKEYS.PLAID_LINK_TOKEN, + initWithStoredValues: false, }, }), )(AddPlaidBankAccount); diff --git a/src/components/Avatar.js b/src/components/Avatar.js index ff51d2c427b8..eb5f28f64efe 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -8,6 +8,7 @@ import themeColors from '../styles/themes/default'; import CONST from '../CONST'; import * as StyleUtils from '../styles/StyleUtils'; import * as Expensicons from './Icon/Expensicons'; +import getAvatarDefaultSource from '../libs/getAvatarDefaultSource'; const propTypes = { /** Source for the avatar. Can be a URL or an icon. */ @@ -70,7 +71,12 @@ class Avatar extends PureComponent { /> ) : ( - this.setState({imageError: true})} /> + this.setState({imageError: true})} + /> )} ); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 387f58bc6c6b..2c9ef4a3ad33 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -86,8 +86,10 @@ class EmojiPickerMenu extends Component { this.isMobileLandscape = this.isMobileLandscape.bind(this); this.onSelectionChange = this.onSelectionChange.bind(this); this.updatePreferredSkinTone = this.updatePreferredSkinTone.bind(this); + this.setFirstNonHeaderIndex = this.setFirstNonHeaderIndex.bind(this); this.currentScrollOffset = 0; + this.firstNonHeaderIndex = 0; this.state = { filteredEmojis: this.emojis, @@ -111,6 +113,7 @@ class EmojiPickerMenu extends Component { this.props.forwardedRef(this.searchInput); } this.setupEventHandlers(); + this.setFirstNonHeaderIndex(this.emojis); } componentWillUnmount() { @@ -126,6 +129,14 @@ class EmojiPickerMenu extends Component { this.setState({selection: event.nativeEvent.selection}); } + /** + * Find and store index of the first emoji item + * @param {Array} filteredEmojis + */ + setFirstNonHeaderIndex(filteredEmojis) { + this.firstNonHeaderIndex = _.findIndex(filteredEmojis, item => !item.spacer && !item.header); + } + /** * Setup and attach keypress/mouse handlers for highlight navigation. */ @@ -214,10 +225,12 @@ class EmojiPickerMenu extends Component { * @param {String} arrowKey */ highlightAdjacentEmoji(arrowKey) { - const firstNonHeaderIndex = this.state.filteredEmojis.length === this.emojis.length ? this.numColumns : 0; + if (this.state.filteredEmojis.length === 0) { + return; + } // Arrow Down and Arrow Right enable arrow navigation when search is focused - if (this.searchInput && this.searchInput.isFocused() && this.state.filteredEmojis.length) { + if (this.searchInput && this.searchInput.isFocused()) { if (arrowKey !== 'ArrowDown' && arrowKey !== 'ArrowRight') { return; } @@ -242,7 +255,7 @@ class EmojiPickerMenu extends Component { // If nothing is highlighted and an arrow key is pressed // select the first emoji if (this.state.highlightedIndex === -1) { - this.setState({highlightedIndex: firstNonHeaderIndex}); + this.setState({highlightedIndex: this.firstNonHeaderIndex}); this.scrollToHighlightedIndex(); return; } @@ -273,7 +286,7 @@ class EmojiPickerMenu extends Component { break; case 'ArrowLeft': move(-1, - () => this.state.highlightedIndex - 1 < firstNonHeaderIndex, + () => this.state.highlightedIndex - 1 < this.firstNonHeaderIndex, () => { // Reaching start of the list, arrow left set the focus to searchInput. this.focusInputWithTextSelect(); @@ -286,7 +299,7 @@ class EmojiPickerMenu extends Component { case 'ArrowUp': move( -this.numColumns, - () => this.state.highlightedIndex - this.numColumns < firstNonHeaderIndex, + () => this.state.highlightedIndex - this.numColumns < this.firstNonHeaderIndex, () => { // Reaching start of the list, arrow up set the focus to searchInput. this.focusInputWithTextSelect(); @@ -356,6 +369,7 @@ class EmojiPickerMenu extends Component { headerIndices: this.unfilteredHeaderIndices, highlightedIndex: -1, }); + this.setFirstNonHeaderIndex(this.emojis); return; } @@ -370,6 +384,7 @@ class EmojiPickerMenu extends Component { // Remove sticky header indices. There are no headers while searching and we don't want to make emojis sticky this.setState({filteredEmojis: newFilteredEmojiList, headerIndices: [], highlightedIndex: 0}); + this.setFirstNonHeaderIndex(newFilteredEmojiList); } /** diff --git a/src/components/ScreenWrapper.js b/src/components/ScreenWrapper.js index 9167c7cc2dd7..bc6a416dbd01 100644 --- a/src/components/ScreenWrapper.js +++ b/src/components/ScreenWrapper.js @@ -1,7 +1,7 @@ import _ from 'underscore'; import React from 'react'; import PropTypes from 'prop-types'; -import {View, KeyboardAvoidingView} from 'react-native'; +import {View} from 'react-native'; import {SafeAreaInsetsContext} from 'react-native-safe-area-context'; import {withOnyx} from 'react-native-onyx'; import styles from '../styles/styles'; @@ -15,10 +15,6 @@ import compose from '../libs/compose'; import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import withNavigation from './withNavigation'; -import withWindowDimensions from './withWindowDimensions'; -import OfflineIndicator from './OfflineIndicator'; -import {withNetwork} from './OnyxProvider'; -import networkPropTypes from './networkPropTypes'; const propTypes = { /** Array of additional styles to add */ @@ -39,13 +35,6 @@ const propTypes = { // Called when navigated Screen's transition is finished. onTransitionEnd: PropTypes.func, - /** Is the window width narrow, like on a mobile device */ - isSmallScreenWidth: PropTypes.bool.isRequired, - - /** The behavior to pass to the KeyboardAvoidingView, requires some trial and error depending on the layout/devices used. - * Search 'switch(behavior)' in ./node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js for more context */ - keyboardAvoidingViewBehavior: PropTypes.oneOf(['padding', 'height', 'position']), - // react-navigation navigation object available to screen components navigation: PropTypes.shape({ // Method to attach listener to Navigation state. @@ -58,9 +47,6 @@ const propTypes = { willAlertModalBecomeVisible: PropTypes.bool, }), - /** Information about the network */ - network: networkPropTypes.isRequired, - }; const defaultProps = { @@ -72,7 +58,6 @@ const defaultProps = { addListener: () => {}, }, modal: {}, - keyboardAvoidingViewBehavior: 'padding', }; class ScreenWrapper extends React.Component { @@ -120,36 +105,27 @@ class ScreenWrapper extends React.Component { paddingStyle.paddingTop = paddingTop; } - // We always need the safe area padding bottom if we're showing the offline indicator since it is bottom-docked. - if (this.props.includePaddingBottom || this.props.network.isOffline) { + if (this.props.includePaddingBottom) { paddingStyle.paddingBottom = paddingBottom; } return ( - - - - {// If props.children is a function, call it to provide the insets to the children. - _.isFunction(this.props.children) - ? this.props.children({ - insets, - didScreenTransitionEnd: this.state.didScreenTransitionEnd, - }) - : this.props.children - } - - {this.props.isSmallScreenWidth && this.props.network.isOffline && ( - - - - )} - + + {// If props.children is a function, call it to provide the insets to the children. + _.isFunction(this.props.children) + ? this.props.children({ + insets, + didScreenTransitionEnd: this.state.didScreenTransitionEnd, + }) + : this.props.children + } + ); }} @@ -163,11 +139,9 @@ ScreenWrapper.defaultProps = defaultProps; export default compose( withNavigation, - withWindowDimensions, withOnyx({ modal: { key: ONYXKEYS.MODAL, }, }), - withNetwork(), )(ScreenWrapper); diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 2a79f6b0a055..a083af0f2e16 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -14,7 +14,6 @@ import * as Expensicons from '../Icon/Expensicons'; import Text from '../Text'; import * as styleConst from './styleConst'; import * as StyleUtils from '../../styles/StyleUtils'; -import variables from '../../styles/variables'; import getSecureEntryKeyboardType from '../../libs/getSecureEntryKeyboardType'; class BaseTextInput extends Component { @@ -31,7 +30,6 @@ class BaseTextInput extends Component { passwordHidden: props.secureTextEntry, textInputWidth: 0, prefixWidth: 0, - height: variables.componentSizeLarge, // Value should be kept in state for the autoGrow feature to work - https://github.com/Expensify/App/pull/8232#issuecomment-1077282006 value, @@ -214,7 +212,6 @@ class BaseTextInput extends Component { > this.setState({height: event.nativeEvent.layout.height})} style={[ textInputContainerStyles, @@ -267,7 +264,6 @@ class BaseTextInput extends Component { !hasLabel && styles.pv0, this.props.prefixCharacter && StyleUtils.getPaddingLeft(this.state.prefixWidth + styles.pl1.paddingLeft), this.props.secureTextEntry && styles.secureInput, - {height: this.state.height}, ]} multiline={this.props.multiline} maxLength={this.props.maxLength} diff --git a/src/languages/en.js b/src/languages/en.js index fa86c37a450d..204efa8fb27d 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -393,7 +393,6 @@ export default { notOwnerOfFund: 'There was an error setting this card as your default payment method.', setDefaultFailure: 'Something went wrong. Please chat with Concierge for further assistance.', }, - addBankAccountSuccess: 'Your bank account has successfully been added.', addBankAccountFailure: 'And unexpected error occurred while trying to add your bank account. Please try again.', }, transferAmountPage: { @@ -569,6 +568,8 @@ export default { enterPassword: 'Enter Expensify password', alreadyAdded: 'This account has already been added.', chooseAccountLabel: 'Account', + successTitle: 'Personal bank account added!', + successMessage: 'Congrats, your bank account is set up and ready to receive reimbursements.', }, attachmentView: { unknownFilename: 'Unknown filename', diff --git a/src/languages/es.js b/src/languages/es.js index 934df4f03e1c..ea065baa7f43 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -393,7 +393,6 @@ export default { notOwnerOfFund: 'Ha ocurrido un error al establecer esta tarjeta de crédito como tu método de pago predeterminado.', setDefaultFailure: 'No se ha podido configurar el método de pago.', }, - addBankAccountSuccess: 'Su cuenta bancaria ha sido añadida con éxito.', addBankAccountFailure: 'Y ocurrió un error inesperado al intentar agregar su cuenta bancaria. Inténtalo de nuevo.', }, transferAmountPage: { @@ -569,6 +568,8 @@ export default { enterPassword: 'Escribe tu contraseña de Expensify', alreadyAdded: 'Esta cuenta ya ha sido agregada.', chooseAccountLabel: 'Cuenta', + successTitle: '¡Cuenta bancaria personal añadida!', + successMessage: 'Enhorabuena, tu cuenta bancaria está lista para recibir reembolsos.', }, attachmentView: { unknownFilename: 'Archivo desconocido', diff --git a/src/libs/Navigation/CustomActions.js b/src/libs/Navigation/DeprecatedCustomActions.js similarity index 99% rename from src/libs/Navigation/CustomActions.js rename to src/libs/Navigation/DeprecatedCustomActions.js index c5fb5f2a4154..1c192d60f93e 100644 --- a/src/libs/Navigation/CustomActions.js +++ b/src/libs/Navigation/DeprecatedCustomActions.js @@ -16,6 +16,7 @@ function getActiveState() { /** * Go back to the Main Drawer + * @deprecated * @param {Object} navigationRef */ function navigateBackToRootDrawer() { @@ -64,6 +65,7 @@ function getScreenNameFromState(state) { * * More context here: https://github.com/react-navigation/react-navigation/issues/9744 * + * @deprecated * @param {String} route * @returns {Function} */ diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js index c19f10c635d8..6891244a2760 100644 --- a/src/libs/Navigation/Navigation.js +++ b/src/libs/Navigation/Navigation.js @@ -5,7 +5,7 @@ import Onyx from 'react-native-onyx'; import Log from '../Log'; import linkTo from './linkTo'; import ROUTES from '../../ROUTES'; -import CustomActions from './CustomActions'; +import DeprecatedCustomActions from './DeprecatedCustomActions'; import ONYXKEYS from '../../ONYXKEYS'; import linkingConfig from './linkingConfig'; import navigationRef from './navigationRef'; @@ -140,7 +140,7 @@ function navigate(route = ROUTES.HOME) { } if (isDrawerRoute(route)) { - navigationRef.current.dispatch(CustomActions.pushDrawerRoute(route)); + navigationRef.current.dispatch(DeprecatedCustomActions.pushDrawerRoute(route)); return; } @@ -161,7 +161,7 @@ function dismissModal(shouldOpenDrawer = false) { ? shouldOpenDrawer : false; - CustomActions.navigateBackToRootDrawer(); + DeprecatedCustomActions.navigateBackToRootDrawer(); if (normalizedShouldOpenDrawer) { openDrawer(); } diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 53ae93cf8b74..ad98e8fac8f5 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -89,10 +89,6 @@ Onyx.connect({ }, }); -// We are initializing a default avatar here so that we use the same default color for each user we are inviting. This -// will update when the OptionsListUtils re-loads. But will stay the same color for the life of the JS session. -const defaultAvatarForUserToInvite = ReportUtils.getDefaultAvatar(); - /** * Adds expensify SMS domain (@expensify.sms) if login is a phone number and if it's not included yet * @@ -591,7 +587,7 @@ function getOptions(reports, personalDetails, activeReportID, { userToInvite = createOption([login], personalDetails, null, { showChatPreviewLine, }); - userToInvite.icons = [defaultAvatarForUserToInvite]; + userToInvite.icons = [ReportUtils.getDefaultAvatar(login)]; } // If we are prioritizing 1:1 chats in search, do it only once we started searching diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 1c9ed327a1d1..8fb86b3c25dd 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -85,7 +85,7 @@ function addPersonalBankAccount(account, password) { value: { loading: false, error: '', - success: Localize.translateLocal('paymentsPage.addBankAccountSuccess'), + shouldShowSuccess: true, }, }, ], diff --git a/src/libs/getAvatarDefaultSource/index.js b/src/libs/getAvatarDefaultSource/index.js new file mode 100644 index 000000000000..54c2a7d04b01 --- /dev/null +++ b/src/libs/getAvatarDefaultSource/index.js @@ -0,0 +1,7 @@ +/** + * Avatar icon flickers when message is sent for the first time, return and set the source as + * defaultSource prop of image to prevent avatar icon from flicker when running on Web/Desktop + * @param {String|Function} source The source of avatar image + * @return {Object} The image source + */ +export default source => ({uri: source}); diff --git a/src/libs/getAvatarDefaultSource/index.native.js b/src/libs/getAvatarDefaultSource/index.native.js new file mode 100644 index 000000000000..1c1c79caf151 --- /dev/null +++ b/src/libs/getAvatarDefaultSource/index.native.js @@ -0,0 +1,5 @@ +/** + * Avatar icon does not flicker when running on Native, return and set undefined as defaultSource prop of image + * @return {Object} undefined + */ +export default () => undefined; diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js index 6d6586fa1f0e..dc5245702c88 100644 --- a/src/pages/AddPersonalBankAccountPage.js +++ b/src/pages/AddPersonalBankAccountPage.js @@ -15,7 +15,11 @@ import compose from '../libs/compose'; import ONYXKEYS from '../ONYXKEYS'; import Text from '../components/Text'; import styles from '../styles/styles'; +import * as Illustrations from '../components/Icon/Illustrations'; +import Icon from '../components/Icon'; +import defaultTheme from '../styles/themes/default'; import Button from '../components/Button'; +import FixedFooter from '../components/FixedFooter'; import FormScrollView from '../components/FormScrollView'; import FormAlertWithSubmitButton from '../components/FormAlertWithSubmitButton'; import FormHelper from '../libs/FormHelper'; @@ -28,7 +32,7 @@ const propTypes = { ...withLocalizePropTypes, personalBankAccount: PropTypes.shape({ error: PropTypes.string, - success: PropTypes.string, + shouldShowSuccess: PropTypes.bool, loading: PropTypes.bool, }), }; @@ -36,7 +40,7 @@ const propTypes = { const defaultProps = { personalBankAccount: { error: '', - success: '', + shouldShowSuccess: false, loading: false, }, }; @@ -118,7 +122,7 @@ class AddPersonalBankAccountPage extends React.Component { } render() { - const success = lodashGet(this.props, 'personalBankAccount.success', ''); + const shouldShowSuccess = lodashGet(this.props, 'personalBankAccount.shouldShowSuccess', false); const error = lodashGet(this.props, 'personalBankAccount.error', ''); const loading = lodashGet(this.props, 'personalBankAccount.loading', false); @@ -130,18 +134,33 @@ class AddPersonalBankAccountPage extends React.Component { shouldShowBackButton onBackButtonPress={Navigation.goBack} /> - {success ? ( + {shouldShowSuccess ? ( <> - - {success} - - + + + + + {this.props.translate('addPersonalBankAccountPage.successTitle')} + + + {this.props.translate('addPersonalBankAccountPage.successMessage')} + + + +