Skip to content

Commit

Permalink
Merge pull request #1 from Expensify/master
Browse files Browse the repository at this point in the history
Merge to origin master
  • Loading branch information
tugbadogan authored Feb 1, 2021
2 parents ba5ba2e + aadc08e commit a9d5e21
Show file tree
Hide file tree
Showing 48 changed files with 1,080 additions and 225 deletions.
3 changes: 0 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/**
* Rename this file to `.env` and put your local config in here
*/
EXPENSIFY_URL_COM=https://www.expensify.com.dev/
EXPENSIFY_URL_CASH=https://expensify.cash/
EXPENSIFY_PARTNER_NAME=android
Expand Down
23 changes: 12 additions & 11 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ The GitHub workflows require a large list of secrets to deploy, notify and test
4. `ios/Certificates.p12.gpg`
2. `SLACK_WEBHOOK` - Sends Slack notifications via Slack WebHook https://expensify.slack.com/services/B01AX48D7MM
3. `OS_BOTIFY_TOKEN` - Personal access token for @OSBotify user in GitHub
4. `CSC_LINK` - Required to be set for desktop code signing: https://www.electron.build/code-signing.html#travis-appveyor-and-other-ci-servers
5. `CSC_KEY_PASSWORD` - Required to be set for desktop code signing: https://www.electron.build/code-signing.html#travis-appveyor-and-other-ci-servers
6. `APPLE_ID` - Required for notarizing desktop code in `desktop/notarize.js`
7. `APPLE_ID_PASSWORD` - Required for notarizing desktop code in `desktop/notarize.js`
8. `AWS_ACCESS_KEY_ID` - Required for hosting website and desktop compiled code
9. `AWS_SECRET_ACCESS_KEY` - Required for hosting website and desktop compiled code
10. `CLOUDFLARE_TOKEN` - Required for hosting website
11. `APPLE_CONTACT_EMAIL` - Email used for contact between Expensify and Apple for https://appstoreconnect.apple.com/
12. `APPLE_CONTACT_PHONE` - Phone number used for contact between Expensify and Apple for https://appstoreconnect.apple.com/
13. `APPLE_DEMO_EMAIL` - Demo account email used for https://appstoreconnect.apple.com/
14. `APPLE_DEMO_PASSWORD` - Demo account password used for https://appstoreconnect.apple.com/
4. `CLA_BOTIFY_TOKEN` - Personal access token for @CLABotify user in GitHub
5. `CSC_LINK` - Required to be set for desktop code signing: https://www.electron.build/code-signing.html#travis-appveyor-and-other-ci-servers
6. `CSC_KEY_PASSWORD` - Required to be set for desktop code signing: https://www.electron.build/code-signing.html#travis-appveyor-and-other-ci-servers
7. `APPLE_ID` - Required for notarizing desktop code in `desktop/notarize.js`
8. `APPLE_ID_PASSWORD` - Required for notarizing desktop code in `desktop/notarize.js`
9. `AWS_ACCESS_KEY_ID` - Required for hosting website and desktop compiled code
10. `AWS_SECRET_ACCESS_KEY` - Required for hosting website and desktop compiled code
11. `CLOUDFLARE_TOKEN` - Required for hosting website
12. `APPLE_CONTACT_EMAIL` - Email used for contact between Expensify and Apple for https://appstoreconnect.apple.com/
13. `APPLE_CONTACT_PHONE` - Phone number used for contact between Expensify and Apple for https://appstoreconnect.apple.com/
14. `APPLE_DEMO_EMAIL` - Demo account email used for https://appstoreconnect.apple.com/
15. `APPLE_DEMO_PASSWORD` - Demo account password used for https://appstoreconnect.apple.com/

## Actions

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cla.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
uses: cla-assistant/github-action@c89158d361bea4a5a06ff7781b6c4e8aa49dbcc9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PERSONAL_ACCESS_TOKEN : ${{ secrets.OS_BOTIFY_TOKEN }}
PERSONAL_ACCESS_TOKEN : ${{ secrets.CLA_BOTIFY_TOKEN }}
with:
path-to-signatures: '${{ github.repository }}/cla.json'
path-to-document: 'https://github.com/${{ github.repository }}/blob/master/CLA.md'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
# Local development
These instructions should get you set up ready to work on Expensify.cash 🙌

## Getting Started
## Getting Started
1. Install `node` & `npm`: `brew install node`
2. Install `watchman`: `brew install watchman`
3. Install dependencies: `npm install`
Expand Down Expand Up @@ -101,7 +101,7 @@ This is a persistent storage solution wrapped in a Pub/Sub library. In general t

- Onyx stores and retrieves data from persistent storage
- Data is stored as key/value pairs, where the value can be anything from a single piece of data to a complex object
- Collections of data are usually not stored as a single key (eg. an array with multiple objects), but as individual keys+ID (eg. `report_1234`, `report_4567`, etc.). Store collections as individual keys when a component will bind directly to one of those keys. For example: reports are stored as individual keys because `ChatLinkRow.js` binds to the individual report keys for each link. However, report actions are stored as an array of objects because nothing binds directly to a single report action.
- Collections of data are usually not stored as a single key (eg. an array with multiple objects), but as individual keys+ID (eg. `report_1234`, `report_4567`, etc.). Store collections as individual keys when a component will bind directly to one of those keys. For example: reports are stored as individual keys because `OptionRow.js` binds to the individual report keys for each link. However, report actions are stored as an array of objects because nothing binds directly to a single report action.
- Onyx allows other code to subscribe to changes in data, and then publishes change events whenever data is changed
- Anything needing to read Onyx data needs to:
1. Know what key the data is stored in (for web, you can find this by looking in the JS console > Application > local storage)
Expand Down
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
versionCode 386
versionName "1.0.1-385"
versionCode 398
versionName "1.0.1-397"
}
splits {
abi {
Expand Down
1 change: 1 addition & 0 deletions config/webpack/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = {
patterns: [
{from: 'web/favicon.png'},
{from: 'web/favicon-unread.png'},
{from: 'web/og-preview-image.png'},
{from: 'assets/css', to: 'css'},

// These files are copied over as per instructions here
Expand Down
2 changes: 1 addition & 1 deletion ios/ExpensifyCash/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>386</string>
<string>398</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false />
<key>LSRequiresIPhoneOS</key>
Expand Down
2 changes: 1 addition & 1 deletion ios/ExpensifyCashTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>386</string>
<string>398</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "expensify.cash",
"version": "1.0.1-385",
"version": "1.0.1-397",
"author": "Expensify, Inc.",
"homepage": "https://expensify.cash",
"description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
Expand Down
4 changes: 4 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ const CONST = {
REPORT: 'report',
PERSONAL_DETAIL: 'personalDetail',
},
REPORT: {
MAXIMUM_PARTICIPANTS: 8,
},
MODAL: {
MODAL_TYPE: {
CENTERED: 'centered',
BOTTOM_DOCKED: 'bottom_docked',
POPOVER: 'popover',
RIGHT_DOCKED: 'right_docked',
},
},
TIMING: {
Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export default {
HOME: '/home',
SETTINGS: '/settings',
NEW_GROUP: '/new/group',
REPORT: '/r/:reportID',
getReportRoute: reportID => `/r/${reportID}`,
ROOT: '/',
Expand Down
16 changes: 12 additions & 4 deletions src/components/AttachmentModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ class AttachmentModal extends Component {
file: null,
sourceURL: props.sourceURL,
};

this.submitAndClose = this.submitAndClose.bind(this);
}

/**
* Execute the onConfirm callback and close the modal.
*/
submitAndClose() {
this.props.onConfirm(this.state.file);
this.setState({isModalOpen: false});
}

render() {
Expand All @@ -74,6 +84,7 @@ class AttachmentModal extends Component {
<>
<ModalWithHeader
type={CONST.MODAL.MODAL_TYPE.CENTERED}
onSubmit={this.submitAndClose}
onClose={() => this.setState({isModalOpen: false})}
isVisible={this.state.isModalOpen}
title={this.props.title}
Expand All @@ -90,10 +101,7 @@ class AttachmentModal extends Component {
<TouchableOpacity
style={[styles.button, styles.buttonSuccess, styles.buttonConfirm]}
underlayColor={themeColors.componentBG}
onPress={() => {
this.props.onConfirm(this.state.file);
this.setState({isModalOpen: false});
}}
onPress={this.submitAndClose}
>
<Text
style={[
Expand Down
17 changes: 15 additions & 2 deletions src/components/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,34 @@ const propTypes = {
// Modal contents
children: PropTypes.node.isRequired,

// Callback method fired when the user requests to submit the modal content.
onSubmit: PropTypes.func,

// Style of modal to display
type: PropTypes.oneOf([
CONST.MODAL.MODAL_TYPE.CENTERED,
CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED,
CONST.MODAL.MODAL_TYPE.POPOVER,
CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED,
]),

...windowDimensionsPropTypes,
};

const defaultProps = {
onSubmit: null,
type: '',
};

const Modal = (props) => {
const subscribeToKeyEvents = () => {
KeyboardShortcut.subscribe('Escape', props.onClose, [], true);
KeyboardShortcut.subscribe('Enter', props.onSubmit, [], true);
};
const unsubscribeFromKeyEvents = () => {
KeyboardShortcut.unsubscribe('Escape');
KeyboardShortcut.unsubscribe('Enter');
};
const {
modalStyle,
modalContainerStyle,
Expand All @@ -49,8 +62,8 @@ const Modal = (props) => {
<ReactNativeModal
onBackdropPress={props.onClose}
onBackButtonPress={props.onClose}
onModalShow={() => KeyboardShortcut.subscribe('Escape', props.onClose, [], true)}
onModalHide={() => KeyboardShortcut.unsubscribe('Escape')}
onModalShow={subscribeToKeyEvents}
onModalHide={unsubscribeFromKeyEvents}
onSwipeComplete={props.onClose}
swipeDirection={swipeDirection}
isVisible={props.isVisible}
Expand Down
109 changes: 62 additions & 47 deletions src/components/OptionsList.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,57 @@
import _ from 'underscore';
import React from 'react';
import {View, SectionList} from 'react-native';
import React, {forwardRef} from 'react';
import {View, SectionList, Text} from 'react-native';
import PropTypes from 'prop-types';
import styles from '../styles/styles';
import KeyboardSpacer from './KeyboardSpacer';
import ChatLinkRow from '../pages/home/sidebar/ChatLinkRow';
import OptionRow from '../pages/home/sidebar/OptionRow';
import optionPropTypes from './optionPropTypes';

const propTypes = {
/** Extra styles for the section list container */
// Extra styles for the section list container
contentContainerStyles: PropTypes.arrayOf(PropTypes.object),

/** Sections for the section list */
// Sections for the section list
sections: PropTypes.arrayOf(PropTypes.shape({
// Title of the section
title: PropTypes.string,

// The initial index of this section given the total number of options in each section's data array
indexOffset: PropTypes.number,
data: PropTypes.arrayOf(PropTypes.shape({})),

// Array of options
data: PropTypes.arrayOf(optionPropTypes),

// Whether this section should show or not
shouldShow: PropTypes.bool,
})),

/** Index for option to focus on */
// Index for option to focus on
focusedIndex: PropTypes.number,

/** Array of already selected options */
selectedOptions: PropTypes.arrayOf(PropTypes.shape({
/** Text to display */
text: PropTypes.string,

/** Alternate text to display */
alternateText: PropTypes.string,

/** Array of icon urls */
icons: PropTypes.arrayOf(PropTypes.string),

/** Login (only present when there is a single participant) */
login: PropTypes.string,

/** reportID (only present when there is a matching report) */
reportID: PropTypes.number,

/** Whether the report has read or not */
isUnread: PropTypes.bool,

/** Whether the report has a draft comment or not */
hasDraftComment: PropTypes.bool,
// Array of already selected options
selectedOptions: PropTypes.arrayOf(optionPropTypes),

/** Key used internally by React */
keyForList: PropTypes.string,

/** Search text we use to filter options */
searchText: PropTypes.string,

/** Whether the report is pinned or not */
isPinned: PropTypes.bool,
})),

/** Whether we can select multiple options or not */
// Whether we can select multiple options or not
canSelectMultipleOptions: PropTypes.bool,

/** Whether to show headers above each section or not */
// Whether to show headers above each section or not
hideSectionHeaders: PropTypes.bool,

/** Callback to fire when a row is selected */
// Callback to fire when a row is selected
onSelectRow: PropTypes.func,

// Optional header title
headerTitle: PropTypes.string,

// Optional header message
headerMessage: PropTypes.string,

// Passed via forwardRef so we can access the SectionList ref
innerRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({current: PropTypes.instanceOf(SectionList)}),
]),
};

const defaultProps = {
Expand All @@ -72,6 +62,9 @@ const defaultProps = {
canSelectMultipleOptions: false,
hideSectionHeaders: false,
onSelectRow: () => {},
headerMessage: '',
headerTitle: '',
innerRef: null,
};

const OptionsList = ({
Expand All @@ -82,31 +75,49 @@ const OptionsList = ({
canSelectMultipleOptions,
hideSectionHeaders,
onSelectRow,
headerMessage,
headerTitle,
innerRef,
}) => (
<View style={[styles.flex1]}>
{headerMessage ? (
<View style={[styles.p3]}>
{headerTitle ? (
<Text style={[styles.h4, styles.mb1]}>
{headerTitle}
</Text>
) : null}

<Text style={[styles.textLabel]}>
{headerMessage}
</Text>
</View>
) : null}
<SectionList
ref={innerRef}
bounces={false}
indicatorStyle="white"
keyboardShouldPersistTaps="always"
contentContainerStyle={[...contentContainerStyles]}
showsVerticalScrollIndicator={false}
sections={sections}
keyExtractor={option => option.keyForList}
initialNumToRender={200}
initialNumToRender={500}
onScrollToIndexFailed={error => console.debug(error)}
renderItem={({item, index, section}) => (
<ChatLinkRow
<OptionRow
option={item}
optionIsFocused={focusedIndex === (index + section.indexOffset)}
onSelectRow={onSelectRow}
isSelected={_.find(selectedOptions, option => option.login === item.login)}
isSelected={Boolean(_.find(selectedOptions, option => option.login === item.login))}
showSelectedState={canSelectMultipleOptions}
/>
)}
renderSectionHeader={({section: {title, shouldShow}}) => {
if (title && shouldShow && !hideSectionHeaders) {
return (
<View>
<Text style={styles.subHeader}>
<Text style={[styles.p5, styles.textMicroBold]}>
{title}
</Text>
</View>
Expand All @@ -124,4 +135,8 @@ const OptionsList = ({
OptionsList.propTypes = propTypes;
OptionsList.displayName = 'OptionsList';
OptionsList.defaultProps = defaultProps;
export default OptionsList;

export default forwardRef((props, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<OptionsList {...props} innerRef={ref} />
));
Loading

0 comments on commit a9d5e21

Please sign in to comment.