Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/mollfpr/App into f-9124
Browse files Browse the repository at this point in the history
  • Loading branch information
mollfpr committed Jun 13, 2022
2 parents 66fcab9 + 6bac9af commit 4d3f6aa
Show file tree
Hide file tree
Showing 110 changed files with 2,714 additions and 1,310 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.github/workflows/ @AndrewGable @coleaeason @rafecolton
# Every PR gets a review from an internal Expensify engineer
* @Expensify/pullerbear

# Performance sensitive and problem areas
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/platformDeploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ jobs:
- name: Run Fastlane beta
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }}
run: bundle exec fastlane android beta
env:
MYAPP_UPLOAD_STORE_PASSWORD: ${{ secrets.MYAPP_UPLOAD_STORE_PASSWORD }}
MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }}


- name: Run Fastlane production
if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }}
Expand Down
3 changes: 3 additions & 0 deletions .well-known/apple-app-site-association
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@
]
}
]
},
"webcredentials": {
"apps": ["368M544MTT.com.chat.expensify.chat"]
}
}
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Additionally if you want to discuss an idea with the community without having a
- Note: Issues that have not had the `External` label applied have not yet been approved for implementation. This means, if you propose a solution to an issue without the `External` label (which you are allowed to do) it is possible that the issue will be fixed internally. If the `External` label has not yet been applied, Expensify has the right to use your proposal to fix said issue, without providing compensation for your solution. This process covers the very rare instance where we need or want to fix an issue internally.
- Note: Before submitting a proposal on an issue, be sure to read any other existing proposals. Any new proposal should be substantively different from existing proposals.
5. Pause at this step until someone from the Contributor-Plus team and / or someone from Expensify provides feedback on your proposal (do not create a pull request yet).
6. If your solution proposal is accepted, Expensify will hire you on Upwork and assign the GitHub issue to you.
6. If your solution proposal is accepted by the Expensify engineer assigned to the issue, Expensify will hire you on Upwork and assign the GitHub issue to you.

#### Begin coding your solution in a pull request
7. When you are ready to start, fork the repository and create a new branch.
Expand Down
2 changes: 1 addition & 1 deletion FORMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,4 @@ Form.js will automatically provide the following props to any input with the inp
- defaultValue: The input default value.
- errorText: The translated error text that is returned by validate for that specific input.
- onBlur: An onBlur handler that calls validate.
- onChange: An onChange handler that saves draft values and calls validate.
- onInputChange: An onChange handler that saves draft values and calls validate for that input (inputA). Passing an inputID as a second param allows inputA to manipulate the input value of the provided inputID (inputB).
133 changes: 133 additions & 0 deletions OfflineUX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#### Offline UX Patterns

### Contents
* [Motivation & Philosophy](#motivation-&-philosophy)
* [UX Pattern Flowchart](#ux-pattern-flowchart)
* [Answering Questions on the Flow Chart](#answering-questions-on-the-flowchart)
* [Description of the Patterns](#description-of-the-patterns)
- [None - No Offline Behavior](#none---no-offline-behavior)
- [A - Optimistic Without Feedback](#a---optimistic-without-feedback)
- [B - Optimistic With Feedback](#b---optimistic-with-feedback)
- [C - Blocking Form](#c---blocking-form)
- [D - Full Page Blocking](#d---full-page-blocking)

### Motivation & Philosophy

Understanding the offline behavior of our app is vital to becoming a productive contributor to the Expensify codebase. Our mission is to support our users in every possible environment, and often our app is used in places where a stable internet connection is not guaranteed.

The most important concept to keep in mind while reading this document is that we want to allow users to do as much as possible when offline. At first, this might seem impossible because almost everything the user can touch in our app is related to an API request. However, in many cases, we can save that API request and assume it will succeed when the user is back online. We then allow the user to proceed as if their request already succeeded. We call this an optimistic response. Here, we use the word **optimistic** to indicate that we're confident the request will succeed when the user is online, and we know what that successful response will look like.

<hr />
Example: Pinning a chat

When a user clicks the pin button <img style="height: 10px; width: 10px;" src="./assets/images/pin.svg"/> on a chat, two things should happen.

1. **API Request:** We send a request to the API to ensure the change is saved in the database. This way the chat is pinned on all the user's devices, and will remain pinned even if they leave the app and come back.

2. **UI Changes:** The chat should go to the top of the list with the other pinned chats, and the pin button should look darker than it did before. This is visual feedback that clicking the pin button worked.

If the user is offline, we don't need to wait for the API request to finish before doing all that visual stuff because this particular API request has almost no way of failing, and we know what the server will return in advance. That means we can safely assume that when we retry the command, it will succeed and that's why we let the user continue using the app as if the action succeeded.

<hr />

The example we just looked at is nice and simple, but some actions should not use this approach (example: requesting money from another user). For these types of actions, we can't simply proceed as if the request already finished. Here are some reasons why:

1. We don't know _how_ to proceed because of a lack of information (often the server returns data that we wouldn't be able to guess the content of).

2. We may be able to guess what a successful request would look like, but we don't want to misguide the user into believing their action was completed. For example, we don't want the user to believe that a financial transaction has been made when it actually hasn't.

To handle problems like this, we have developed offline UX patterns and guidance on when to use them. Every feature of this application should fit into one of these patterns.

### Descriptions of the UX Patterns

# None - No Offline Behavior

There’s no specific UI for this case. The feature either looks totally normal and works as expected (because it doesn’t need the server to function) or the feature looks like it did whenever connection was lost.

**Used when…**
- there is no interaction with the server in any way
- or data is READ from the server and does not need to show up-to-date data. The user will see stale data until the new data is put into Onyx and then the view updates to show the new data.

**How to implement:** Use [`API.read()`](https://github.com/Expensify/App/blob/3493f3ca3a1dc6cdbf9cb8bd342866fcaf45cf1d/src/libs/API.js#L53-L55).

# A - Optimistic Without Feedback Pattern

This is the pattern where we queue the request to be sent when the user is online and we continue as if the request succeeded.

**Used when…**
- the user should be given instant feedback and
- the user does not need to know when the change is done on the server in the background

**How to implement:** Use [`API.write()`](https://github.com/Expensify/App/blob/3493f3ca3a1dc6cdbf9cb8bd342866fcaf45cf1d/src/libs/API.js#L7-L28) to implement this pattern. For this pattern we should only put `optimisticData` in the options. We don't need successData or failData as we don't care what response comes back at all.

# B - Optimistic WITH Feedback Pattern
This pattern queues the API request, but also makes sure that the user is aware that the request hasn’t been sent yet **when the user is offline**. When the user is online, the feature should just look like it succeeds immediately (we dont want the offline UI to flicker on and off when the user is online).

**Used when…**
- the user needs feedback that data will be sent to the server later
This is a minority use case at the moment, but INCREDIBLY HELPFUL for the user, so proceed with cautious optimism.

**How to implement:** Use API.write() to implement this pattern. Optimistic data should include some pending state for the action that is reflected in the UI. Success/failure data should revert the pending state and/or set a failure state accordingly.

# C - Blocking Form UI Pattern
This pattern greys out the submit button on a form and does not allow the form to be submitted. We also show a "You appear offline" message near the bottom of the screen. Importantly, we _do_ let the user fill out the form fields. That data gets saved locally so they don’t have to fill it out again once online.

**Used when…**
- a form is used to make a WRITE request to the server and
- server has to do some validation of the parameters that can’t be done in the client or
- server response will be unknown so it cannot be done optimistically
- If the request is moving money

**How to implement:** Use the `<FormAlertWithSubmitButton/>` component. This pattern should use the `API.write()` method.

# D - Full Page Blocking UI Pattern
This pattern blocks the user from interacting with an entire page.

**Used when…**
- blocking READ is being performed. This occurs when the data that a user sees cannot be stale data and the data can only be displayed after fetching it from the server (eg. Plaid's list of bank accounts)
- the app is offline and the data cannot be fetched
- an error occurs when fetching the data and the user needs instructions on what to do next
This should only be used in the most extreme cases when all other options have been completely and utterly exhausted

**How to implement:** Wrap the component you're working on in a `<FullPageOfflineBlockingView>` component.

### UX Pattern Flow Chart

The following flowchart can be used to determine which UX pattern should be used.

![New Expensify Data Flow Chart](/web/OfflineUX_Patterns_Flowchart.png)

### Answering Questions on the Flow Chart

The numbers in this section correlate to the numbers in each decision box above (the diamond shapes).

1. Does the feature interact with the server?

If you're changing an existing feature, you can open the network tab of dev tools to see if any network requests are being made when you use the feature. If network requests are being made, the answer to this question is YES. Note: Sometimes you may see requests that happen to fire at the same time as the feature you're working on, so be sure to double check.
If you're making a new feature, think about whether any data would need to be retrieved or stored from anywhere other than the local device. If data needs to be stored to or retrieved from the server, then the answer is YES.

2. What type of request is being made?

If there's new data being saved on the server, you're making a WRITE request. If you're retrieving existing data from the server, you're making a READ request. If both things are happening, that's a WRITE request.

3. Is it OK for the user to see stale data?

Example: The payment method list. We don't want the user to see a payment method that we no longer support, not even while the payment methods are being loaded from the server (or while the user is offline). Therefore, we answer NO, which leads us to the blocking UI. This way the user won't see stale data while we load the payment methods.

4. Is the UI a form?

An easy way to tell if something is a form is to try and find a submit button. If a submit button is present or if the user is filling out form inputs, answer YES to this question.

5. Can the server response be anticipated?

Answer NO if there is data coming back from the server that we can't know (example: a list of bank accounts from Plaid, input validation that the server must perform). Answer YES if we can know what the response from the server would be.

6. Is there validation done on the server that can't be done on the front end?

If there is some validation happening on the server that needs to happen before the feature can work, then we answer YES to this question. Remember, this is referring to validation that cannot happen on the front end (e.g. reusing an existing password when resetting a password). For example, if we want to set up a bank account then our answer to this question is YES (because we can’t suggest to the user that their request succeeded when really it hasn’t been sent yet–their card wouldn’t work!)

This question can be tricky, so if you're unsure, please ask a question in the #expensify-open-source slack room and tag @contributor-management-engineering.

7. Does the user need to know if the action was successful?

Think back to the pinning example from above: the user doesn’t need to know that their pinned report's NVP has been updated. To them the impact of clicking the pin button is that their chat is at the top of the LHN. It makes no difference to them if the server has been updated or not, so the answer would be NO. Now let’s consider sending a payment request to another user. In this example, the user needs to know if their request was actually sent, so our answer is YES.
8 changes: 4 additions & 4 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
versionCode 1001017001
versionName "1.1.70-1"
versionCode 1001017602
versionName "1.1.76-2"
}
splits {
abi {
Expand All @@ -167,9 +167,9 @@ android {
release {
if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
storeFile file(MYAPP_UPLOAD_STORE_FILE)
storePassword MYAPP_UPLOAD_STORE_PASSWORD
storePassword System.getenv('MYAPP_UPLOAD_STORE_PASSWORD')
keyAlias MYAPP_UPLOAD_KEY_ALIAS
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
keyPassword System.getenv('MYAPP_UPLOAD_KEY_PASSWORD')
}
}
debug {
Expand Down
Binary file modified android/app/my-upload-key.keystore.gpg
Binary file not shown.
2 changes: 0 additions & 2 deletions android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ FLIPPER_VERSION=0.100.0

# Key Store Information
MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore
MYAPP_UPLOAD_STORE_PASSWORD=jab-devalue-medley-quartet-ambition-demigod-shinbone-luster-inject-while

# Key Information
MYAPP_UPLOAD_KEY_ALIAS=ReactNativeChat-Key-Alias
MYAPP_UPLOAD_KEY_PASSWORD=pry-eave-cervix-deltoid-plaudit-bunting-larynges-octagon-jetliner-possum
11 changes: 7 additions & 4 deletions assets/images/offline-cloud.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions desktop/notarize.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ exports.default = function notarizing(context) {
const appName = context.packager.appInfo.productFilename;

return notarize({
tool: 'notarytool',
appBundleId: electron.appId,
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
teamId: '368M544MTT',
});
};
6 changes: 2 additions & 4 deletions ios/NewExpensify/Chat.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:www.expensify.cash</string>
<string>applinks:staging.expensify.cash</string>
<string>applinks:staging.new.expensify.com</string>
<string>applinks:expensify.cash</string>
<string>applinks:new.expensify.com</string>
<string>applinks:staging.new.expensify.com</string>
<string>webcredentials:new.expensify.com</string>
</array>
</dict>
</plist>
4 changes: 2 additions & 2 deletions ios/NewExpensify/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.1.70</string>
<string>1.1.76</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
Expand All @@ -30,7 +30,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1.1.70.1</string>
<string>1.1.76.2</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
Expand Down
4 changes: 2 additions & 2 deletions ios/NewExpensifyTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.1.70</string>
<string>1.1.76</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.1.70.1</string>
<string>1.1.76.2</string>
</dict>
</plist>
Loading

0 comments on commit 4d3f6aa

Please sign in to comment.