diff --git a/.github/workflows/README.md b/.github/workflows/README.md index aa38a7778f31..e1b1696411b1 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -104,6 +104,11 @@ The GitHub workflows require a large list of secrets to deploy, notify and test 1. `APPLE_DEMO_PASSWORD` - Demo account password used for https://appstoreconnect.apple.com/ 1. `BROWSERSTACK` - Used to access Browserstack's API +### Important note about Secrets +Secrets are available by default in most workflows. The exception to the rule is callable workflows. If a workflow is triggered by the `workflow_call` event, it will only have access to repo secrets if the workflow that called it passed in the secrets explicitly (for example, using `secrets: inherit`). + +Furthermore, secrets are not accessible in actions. If you need to access a secret in an action, you must declare it as an input and pass it in. GitHub _should_ still obfuscate the value of the secret in workflow run logs. + ## Actions All these _workflows_ are comprised of atomic _actions_. Most of the time, we can use pre-made and independently maintained actions to create powerful workflows that meet our needs. However, when we want to do something very specific or have a more complex or robust action in mind, we can create our own _actions_. diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml index cb4e0f956657..ca7345ef9462 100644 --- a/.github/workflows/deployExpensifyHelp.yml +++ b/.github/workflows/deployExpensifyHelp.yml @@ -28,23 +28,27 @@ jobs: steps: - name: Checkout uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - name: Setup NodeJS uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Pages uses: actions/configure-pages@f156874f8191504dae5b037505266ed5dda6c382 + - name: Create docs routes file run: ./.github/scripts/createDocsRoutes.sh + - name: Build with Jekyll uses: actions/jekyll-build-pages@0143c158f4fa0c5dcd99499a5d00859d79f70b0e with: source: ./docs/ destination: ./docs/_site + - name: Upload artifact uses: actions/upload-pages-artifact@64bcae551a7b18bcb9a09042ddf1960979799187 with: path: ./docs/_site - # Deployment job deploy: environment: diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index fe364b376e3b..d8f9cad138d9 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -46,6 +46,9 @@ jobs: git fetch origin tag ${{ steps.getMostRecentRelease.outputs.VERSION }} --no-tags --depth=1 git switch --detach ${{ steps.getMostRecentRelease.outputs.VERSION }} + - name: Configure MapBox SDK + run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} + - name: Build APK if: ${{ !fromJSON(steps.checkForExistingArtifact.outputs.exists) }} uses: Expensify/App/.github/actions/composite/buildAndroidAPK@main @@ -112,6 +115,9 @@ jobs: - name: Checkout "delta ref" run: git checkout ${{ steps.getDeltaRef.outputs.DELTA_REF }} + - name: Configure MapBox SDK + run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} + - name: Build APK uses: Expensify/App/.github/actions/composite/buildAndroidAPK@main with: diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index e787b26336c5..84f8373ff247 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -36,6 +36,9 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Configure MapBox SDK + run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} + - uses: Expensify/App/.github/actions/composite/setupNode@main - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 @@ -77,7 +80,7 @@ jobs: - name: Upload Android version to Browser Stack if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@./android/app/build/outputs/bundle/release/app-release.aab" + run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@./android/app/build/outputs/bundle/productionRelease/app-production-release.aab" env: BROWSERSTACK: ${{ secrets.BROWSERSTACK }} @@ -144,6 +147,9 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Configure MapBox SDK + run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} + - uses: Expensify/App/.github/actions/composite/setupNode@main - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe234bc8373c..e79a02281ae0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,9 @@ jobs: name: Storybook tests steps: - uses: actions/checkout@v3 + - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Storybook run run: npm run storybook -- --smoke-test --ci diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index e541e2291ae9..16fffcc2c65e 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -11,7 +11,7 @@ on: branches: ['*ci-test/**'] env: - DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_14.2.app/Contents/Developer jobs: validateActor: @@ -103,6 +103,9 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - name: Configure MapBox SDK + run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} + - name: Run Fastlane beta test id: runFastlaneBetaTest run: bundle exec fastlane android build_internal @@ -111,6 +114,8 @@ jobs: S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} S3_BUCKET: ad-hoc-expensify-cash S3_REGION: us-east-1 + MYAPP_UPLOAD_STORE_PASSWORD: ${{ secrets.MYAPP_UPLOAD_STORE_PASSWORD }} + MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }} - uses: actions/upload-artifact@v3 with: @@ -130,6 +135,9 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + - name: Configure MapBox SDK + run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} + - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it run: | cp .env.staging .env.adhoc @@ -138,6 +146,9 @@ jobs: - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Xcode + run: sudo xcode-select -switch /Applications/Xcode_14.2.app + - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 with: ruby-version: '2.7' @@ -151,7 +162,7 @@ jobs: command: cd ios && bundle exec pod install - name: Decrypt profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output chat_expensify_adhoc.mobileprovision chat_expensify_adhoc.mobileprovision.gpg + run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output expensify_chat_adhoc.mobileprovision expensify_chat_adhoc.mobileprovision.gpg env: LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} diff --git a/.github/workflows/verifyPodfile.yml b/.github/workflows/verifyPodfile.yml index 8b715a7047c4..64188769f0bd 100644 --- a/.github/workflows/verifyPodfile.yml +++ b/.github/workflows/verifyPodfile.yml @@ -15,5 +15,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 + - uses: Expensify/App/.github/actions/composite/setupNode@main + - run: ./.github/scripts/verifyPodfile.sh diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association index cf6bdf1dedef..9274dd8c1382 100644 --- a/.well-known/apple-app-site-association +++ b/.well-known/apple-app-site-association @@ -32,6 +32,10 @@ "/": "/iou/*", "comment": "I Owe You reports" }, + { + "/": "/request/*", + "comment": "Money request" + }, { "/": "/enable-payments/*", "comment": "Payments setup" diff --git a/README.md b/README.md index b453a278b29f..f0a94a16855c 100644 --- a/README.md +++ b/README.md @@ -50,13 +50,14 @@ For an M1 Mac, read this [SO](https://stackoverflow.com/c/expensify/questions/11 * Install project gems, including cocoapods, using bundler to ensure everyone uses the same versions. In the project root, run: `bundle install` * If you get the error `Could not find 'bundler'`, install the bundler gem first: `gem install bundler` and try again. * If you are using MacOS and get the error `Gem::FilePermissionError` when trying to install the bundler gem, you're likely using system Ruby, which requires administrator permission to modify. To get around this, install another version of Ruby with a version manager like [rbenv](https://github.com/rbenv/rbenv#installation). +* Before installing iOS dependencies, you need to obtain a token from Mapbox to download their SDKs. Please run `npm run configure-mapbox` and follow the instructions. * To install the iOS dependencies, run: `npm install && npm run pod-install` * If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699) * To run a on a **Development Simulator**: `npm run ios` * Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile ## Running the Android app 🤖 -* To install the Android dependencies, run: `npm install` +* Before installing Android dependencies, you need to obtain a token from Mapbox to download their SDKs. Please run `npm run configure-mapbox` and follow the instructions. If you already did this step for iOS, there is no need to repeat this step. * Go through the instructions on [this SO post](https://stackoverflow.com/c/expensify/questions/13283/13284#13284) to start running the app on android. * For more information, go through the official React-Native instructions on [this page](https://reactnative.dev/docs/environment-setup#development-os) for "React Native CLI Quickstart" > Mac OS > Android * If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699) @@ -418,4 +419,4 @@ In order to compile a production desktop build, run `npm run desktop-build`, thi In order to compile a production iOS build, run `npm run ios-build`, this will generate a `Chat.ipa` in the root directory of this project. #### Local production build the Android app -To build an APK to share run (e.g. via Slack), run `npm run android-build`, this will generate a new APK in the `android/app` folder. +To build an APK to share run (e.g. via Slack), run `npm run android-build`, this will generate a new APK in the `android/app` folder. \ No newline at end of file diff --git a/__mocks__/react-native.js b/__mocks__/react-native.js index 26a943ce62bc..006d1aee38af 100644 --- a/__mocks__/react-native.js +++ b/__mocks__/react-native.js @@ -1,7 +1,6 @@ // eslint-disable-next-line no-restricted-imports import * as ReactNative from 'react-native'; import _ from 'underscore'; -import CONST from '../src/CONST'; jest.doMock('react-native', () => { let url = 'https://new.expensify.com/'; @@ -15,7 +14,12 @@ jest.doMock('react-native', () => { // runs against index.native.js source and so anything that is testing a component reliant on withWindowDimensions() // would be most commonly assumed to be on a mobile phone vs. a tablet or desktop style view. This behavior can be // overridden by explicitly setting the dimensions inside a test via Dimensions.set() - let dimensions = CONST.TESTING.SCREEN_SIZE.SMALL; + let dimensions = { + width: 300, + height: 700, + scale: 1, + fontScale: 1, + }; return Object.setPrototypeOf( { diff --git a/android/app/build.gradle b/android/app/build.gradle index baf35c531fd3..c6c2e308bac2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,7 +22,7 @@ react { // The list of variants to that are debuggable. For those we're going to // skip the bundling of the JS bundle and the assets. By default is just 'debug'. // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. - // debuggableVariants = ["liteDebug", "prodDebug"] + debuggableVariants = ["developmentDebug"] /* Bundling */ // A list containing the node command and its flags. Default is just 'node'. @@ -53,8 +53,12 @@ react { } project.ext.envConfigFiles = [ - debug: ".env", - release: ".env.production", + productionDebug: ".env.production", + productionRelease: ".env.production", + adhocRelease: ".env.adhoc", + developmentRelease: ".env", + developmentDebug: ".env", + e2eRelease: ".env.production" ] /** @@ -86,18 +90,39 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001035301 - versionName "1.3.53-1" + versionCode 1001035703 + versionName "1.3.57-3" + } + + flavorDimensions "default" + productFlavors { + // we need to define a production flavor but since it has default config, we can leave it empty + production + e2e { + // If are building a version that won't be uploaded to the play store, we don't have to use production keys + // applies all non-production flavors + applicationIdSuffix ".adhoc" + signingConfig signingConfigs.debug + resValue "string", "build_config_package", "com.expensify.chat" + } + adhoc { + applicationIdSuffix ".adhoc" + signingConfig signingConfigs.debug + resValue "string", "build_config_package", "com.expensify.chat" + } + development { + applicationIdSuffix ".dev" + signingConfig signingConfigs.debug + resValue "string", "build_config_package", "com.expensify.chat" + } } signingConfigs { release { - if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) { - storeFile file(MYAPP_UPLOAD_STORE_FILE) - storePassword System.getenv('MYAPP_UPLOAD_STORE_PASSWORD') - keyAlias MYAPP_UPLOAD_KEY_ALIAS - keyPassword System.getenv('MYAPP_UPLOAD_KEY_PASSWORD') - } + storeFile file(MYAPP_UPLOAD_STORE_FILE) + storePassword System.getenv('MYAPP_UPLOAD_STORE_PASSWORD') + keyAlias MYAPP_UPLOAD_KEY_ALIAS + keyPassword System.getenv('MYAPP_UPLOAD_KEY_PASSWORD') } debug { storeFile file('debug.keystore') @@ -112,19 +137,16 @@ android { } release { signingConfig signingConfigs.release + productFlavors.production.signingConfig signingConfigs.release minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } - // We need a custom build type, so we can allow http clear text traffic in a release build: - e2eRelease { - initWith release - matchingFallbacks = ['release'] - signingConfig signingConfigs.debug - } - internalRelease { - initWith release - matchingFallbacks = ['release'] - signingConfig signingConfigs.debug + } + + // since we don't need variants adhocDebug and e2eDebug, we can force gradle to ignore them + variantFilter { variant -> + if (variant.name == "adhocDebug" || variant.name == "e2eDebug") { + setIgnore(true) } } } @@ -170,7 +192,7 @@ dependencies { // Fixes a version conflict between airship and react-native-plaid-link-sdk // This may be fixed by a newer version of the plaid SDK (not working as of 10.0.0) implementation "androidx.work:work-runtime-ktx:2.8.0" - + // This okhttp3 dependency prevents the app from crashing - See https://github.com/plaid/react-native-plaid-link-sdk/issues/74#issuecomment-648435002 implementation "com.squareup.okhttp3:okhttp-urlconnection:4.+" @@ -178,8 +200,5 @@ dependencies { } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) -def googleServicesFile = rootProject.file('app/google-services.json') -if (googleServicesFile.exists()) { - apply plugin: 'com.google.gms.google-services' // Google Play services Gradle plugin -} +apply plugin: 'com.google.gms.google-services' // Google Play services Gradle plugin apply plugin: 'com.google.firebase.crashlytics' diff --git a/android/app/google-services.json b/android/app/google-services.json index 545f49d765bf..35f7f5b68921 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1,47 +1,143 @@ { "project_info": { - "project_number": "921154746561", - "firebase_url": "https://expensify-chat.firebaseio.com", - "project_id": "expensify-chat", - "storage_bucket": "expensify-chat.appspot.com" + "project_number": "921154746561", + "firebase_url": "https://expensify-chat.firebaseio.com", + "project_id": "expensify-chat", + "storage_bucket": "expensify-chat.appspot.com" }, "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:921154746561:android:4f04268f25f84eaf027c40", - "android_client_info": { - "package_name": "com.expensify.chat" - } - }, - "oauth_client": [ - { - "client_id": "921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyCVwQb9lBI06bDIwHOw10AkdJyquXoMngk" + { + "client_info": { + "mobilesdk_app_id": "1:921154746561:android:4f04268f25f84eaf027c40", + "android_client_info": { + "package_name": "com.expensify.chat" + } + }, + "oauth_client": [ + { + "client_id": "921154746561-o0pgqgc84e3e97s9iljlmimcb5nesqad.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.expensify.chat", + "certificate_hash": "5e8f16062ea3cd2c4a0d547876baa6f38cabf625" + } + }, + { + "client_id": "921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCVwQb9lBI06bDIwHOw10AkdJyquXoMngk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "921154746561-080fav7kvk6s70k6nd70mt50isubgff4.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.expensify.chat.adhoc" } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "com.chat.expensify.chat" - } - } - ] + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:921154746561:android:333e293a7fef83a8027c40", + "android_client_info": { + "package_name": "com.expensify.chat.adhoc" + } + }, + "oauth_client": [ + { + "client_id": "921154746561-cbegir0tnc2gan6k1gre5vtn75p60hom.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.expensify.chat.adhoc", + "certificate_hash": "5e8f16062ea3cd2c4a0d547876baa6f38cabf625" + } + }, + { + "client_id": "921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCVwQb9lBI06bDIwHOw10AkdJyquXoMngk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "921154746561-080fav7kvk6s70k6nd70mt50isubgff4.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.expensify.chat.adhoc" } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:921154746561:android:3b19fdbaedb5b586027c40", + "android_client_info": { + "package_name": "com.expensify.chat.dev" + } + }, + "oauth_client": [ + { + "client_id": "921154746561-svjnccrcn6vet45kn9o7sibb3jemipa6.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.expensify.chat.dev", + "certificate_hash": "5e8f16062ea3cd2c4a0d547876baa6f38cabf625" } + }, + { + "client_id": "921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCVwQb9lBI06bDIwHOw10AkdJyquXoMngk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "921154746561-080fav7kvk6s70k6nd70mt50isubgff4.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.expensify.chat.adhoc" + } + } + ] + } } + } ], "configuration_version": "1" -} + } diff --git a/android/app/src/adhoc/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/adhoc/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000000..80b730f3673e --- /dev/null +++ b/android/app/src/adhoc/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/adhoc/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/adhoc/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000000..80b730f3673e --- /dev/null +++ b/android/app/src/adhoc/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher.png b/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000000..d76e72f68d43 Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..ecf9a8d7648a Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000000..f8d43cb7dc2d Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/adhoc/res/mipmap-ldpi/ic_launcher.png b/android/app/src/adhoc/res/mipmap-ldpi/ic_launcher.png new file mode 100644 index 000000000000..30c0e8484309 Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-ldpi/ic_launcher.png differ diff --git a/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher.png b/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000000..6767ae1f2712 Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..ba8a2086138c Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000000..3f0d4a9f6b77 Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000000..9a406a263d3d Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..d1e6dbf34a18 Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000000..9ca33d6f0e5c Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000000..819d0456ff8a Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..2bb80c3d622b Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000000..c343ab0f94a5 Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000000..b5d80bc20289 Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..576550530857 Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000000..d6df660bc3c3 Binary files /dev/null and b/android/app/src/adhoc/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/adhoc/res/values/ic_launcher_background.xml b/android/app/src/adhoc/res/values/ic_launcher_background.xml new file mode 100644 index 000000000000..ad6f6d9631c0 --- /dev/null +++ b/android/app/src/adhoc/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #3DDC84 + diff --git a/android/app/src/adhoc/res/values/strings.xml b/android/app/src/adhoc/res/values/strings.xml new file mode 100644 index 000000000000..f59d0656694b --- /dev/null +++ b/android/app/src/adhoc/res/values/strings.xml @@ -0,0 +1,3 @@ + + New Expensify AdHoc + diff --git a/android/app/src/debug/assets/airshipconfig.properties b/android/app/src/development/assets/airshipconfig.properties similarity index 100% rename from android/app/src/debug/assets/airshipconfig.properties rename to android/app/src/development/assets/airshipconfig.properties diff --git a/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000000..80b730f3673e --- /dev/null +++ b/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000000..80b730f3673e --- /dev/null +++ b/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/development/res/mipmap-hdpi/ic_launcher.png b/android/app/src/development/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000000..c1ec7afcfc02 Binary files /dev/null and b/android/app/src/development/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/development/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/development/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..eb5af7cb730f Binary files /dev/null and b/android/app/src/development/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/development/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/development/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000000..c6aad72d89f6 Binary files /dev/null and b/android/app/src/development/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/development/res/mipmap-ldpi/ic_launcher.png b/android/app/src/development/res/mipmap-ldpi/ic_launcher.png new file mode 100644 index 000000000000..380afcaa5369 Binary files /dev/null and b/android/app/src/development/res/mipmap-ldpi/ic_launcher.png differ diff --git a/android/app/src/development/res/mipmap-mdpi/ic_launcher.png b/android/app/src/development/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000000..a06edb8a1406 Binary files /dev/null and b/android/app/src/development/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/development/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/development/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..45ceb6c76b3e Binary files /dev/null and b/android/app/src/development/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/development/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/development/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000000..a05f7659d0de Binary files /dev/null and b/android/app/src/development/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000000..d115c9cc2613 Binary files /dev/null and b/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/development/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/development/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..bde468cb56cf Binary files /dev/null and b/android/app/src/development/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/development/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/development/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000000..7d9fe85bfce5 Binary files /dev/null and b/android/app/src/development/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000000..b6a3e55257ce Binary files /dev/null and b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..fe8e3c4be2c6 Binary files /dev/null and b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000000..f85391b480a3 Binary files /dev/null and b/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000000..a6ba2750e92d Binary files /dev/null and b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000000..3ab898c20c6b Binary files /dev/null and b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000000..44aa87a0e8d0 Binary files /dev/null and b/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/development/res/values/ic_launcher_background.xml b/android/app/src/development/res/values/ic_launcher_background.xml new file mode 100644 index 000000000000..ad6f6d9631c0 --- /dev/null +++ b/android/app/src/development/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #3DDC84 + diff --git a/android/app/src/development/res/values/strings.xml b/android/app/src/development/res/values/strings.xml new file mode 100644 index 000000000000..545b4a07a105 --- /dev/null +++ b/android/app/src/development/res/values/strings.xml @@ -0,0 +1,3 @@ + + New Expensify Dev + diff --git a/android/app/src/e2eRelease/AndroidManifest.xml b/android/app/src/e2e/AndroidManifest.xml similarity index 100% rename from android/app/src/e2eRelease/AndroidManifest.xml rename to android/app/src/e2e/AndroidManifest.xml diff --git a/android/app/src/e2eRelease/java/com/expensify/chat/ReactNativeFlipper.java b/android/app/src/e2eRelease/java/com/expensify/chat/ReactNativeFlipper.java deleted file mode 100644 index d7730e2d4fae..000000000000 --- a/android/app/src/e2eRelease/java/com/expensify/chat/ReactNativeFlipper.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - *

This source code is licensed under the MIT license found in the LICENSE file in the root - * directory of this source tree. - */ -package com.expensify.chat; - -import android.content.Context; -import com.facebook.react.ReactInstanceManager; - -/** - * Class responsible of loading Flipper inside your React Native application. This is the release - * flavor of it so it's empty as we don't want to load Flipper. - */ -public class ReactNativeFlipper { - public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { - // Do nothing as we don't want to initialize Flipper on Release. - } -} diff --git a/android/app/src/internalRelease/assets/airshipconfig.properties b/android/app/src/internalRelease/assets/airshipconfig.properties deleted file mode 100644 index 194c4577de8b..000000000000 --- a/android/app/src/internalRelease/assets/airshipconfig.properties +++ /dev/null @@ -1,7 +0,0 @@ -appKey = 55vypj0ARc6cN09MX7ogtQ -appSecret = EsSaqbdLSvmyC6kSBFJCtQ -inProduction = true - -# Notification Customization -notificationIcon = ic_notification -notificationAccentColor = #2EAAE2 \ No newline at end of file diff --git a/android/app/src/internalRelease/java/com/expensify/chat/ReactNativeFlipper.java b/android/app/src/internalRelease/java/com/expensify/chat/ReactNativeFlipper.java deleted file mode 100644 index 0e3c02f072e6..000000000000 --- a/android/app/src/internalRelease/java/com/expensify/chat/ReactNativeFlipper.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - *

This source code is licensed under the MIT license found in the LICENSE file in the root - * directory of this source tree. - */ -package com.expensify.chat; - -import android.content.Context; -import com.facebook.react.ReactInstanceManager; - -/** - * Class responsible of loading Flipper inside your React Native application. This is the release - * flavor of it so it's empty as we don't want to load Flipper. - */ -public class ReactNativeFlipper { - public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { - // Do nothing as we don't want to initialize Flipper on Release. - } -} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f7b50f62369c..f1c7f65757d6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -60,6 +60,7 @@ + @@ -75,6 +76,7 @@ + diff --git a/android/app/src/e2eRelease/assets/airshipconfig.properties b/android/app/src/main/assets/airshipconfig.properties similarity index 100% rename from android/app/src/e2eRelease/assets/airshipconfig.properties rename to android/app/src/main/assets/airshipconfig.properties diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 036d09bc5fd5..80b730f3673e 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 036d09bc5fd5..80b730f3673e 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml index 4e823a09e75e..ad6f6d9631c0 100644 --- a/android/app/src/main/res/values/ic_launcher_background.xml +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ #3DDC84 - \ No newline at end of file + diff --git a/android/app/src/release/assets/airshipconfig.properties b/android/app/src/release/assets/airshipconfig.properties deleted file mode 100644 index 194c4577de8b..000000000000 --- a/android/app/src/release/assets/airshipconfig.properties +++ /dev/null @@ -1,7 +0,0 @@ -appKey = 55vypj0ARc6cN09MX7ogtQ -appSecret = EsSaqbdLSvmyC6kSBFJCtQ -inProduction = true - -# Notification Customization -notificationIcon = ic_notification -notificationAccentColor = #2EAAE2 \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index c04314a9aa0c..d7e9529ae6dd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -14,6 +14,10 @@ buildscript { multiDexEnabled = true googlePlayServicesVersion = "17.0.0" kotlinVersion = '1.6.20' + + // This property configures the type of Mapbox SDK used by the @rnmapbox/maps library. + // "mapbox" indicates the usage of the Mapbox SDK. + RNMapboxMapsImpl = "mapbox" } repositories { google() @@ -48,5 +52,23 @@ allprojects { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url("$rootDir/../node_modules/react-native/android") } + maven { + // Mapbox SDK requires authentication to download from Mapbox's private Maven repository. + url 'https://api.mapbox.com/downloads/v2/releases/maven' + authentication { + basic(BasicAuthentication) + } + credentials { + // 'mapbox' is the fixed username for Mapbox's Maven repository. + username = 'mapbox' + + // The value for password is read from the 'MAPBOX_DOWNLOADS_TOKEN' gradle property. + // Run "npm run setup-mapbox-sdk" to set this property in «USER_HOME»/.gradle/gradle.properties + + // Example gradle.properties entry: + // MAPBOX_DOWNLOADS_TOKEN=YOUR_SECRET_TOKEN_HERE + password = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: "" + } + } } -} \ No newline at end of file +} diff --git a/assets/emojis/index.js b/assets/emojis/index.js index 3882ac7f0fa6..c8dab36f57d9 100644 --- a/assets/emojis/index.js +++ b/assets/emojis/index.js @@ -15,13 +15,18 @@ const emojiNameTable = _.reduce( {}, ); -const emojiCodeTable = _.reduce( +const emojiCodeTableWithSkinTones = _.reduce( emojis, (prev, cur) => { const newValue = prev; if (!cur.header) { newValue[cur.code] = cur; } + if (cur.types) { + cur.types.forEach((type) => { + newValue[type] = cur; + }); + } return newValue; }, {}, @@ -32,5 +37,5 @@ const localeEmojis = { es: esEmojis, }; -export {emojiNameTable, emojiCodeTable, localeEmojis}; +export {emojiNameTable, emojiCodeTableWithSkinTones, localeEmojis}; export {skinTones, categoryFrequentlyUsed, default} from './common'; diff --git a/assets/images/dot-indicator-unfilled.svg b/assets/images/dot-indicator-unfilled.svg new file mode 100644 index 000000000000..ae131b1c2cba --- /dev/null +++ b/assets/images/dot-indicator-unfilled.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/assets/images/drag-handles.svg b/assets/images/drag-handles.svg new file mode 100644 index 000000000000..ec4fc4ccc672 --- /dev/null +++ b/assets/images/drag-handles.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/assets/images/emptystate__routepending.svg b/assets/images/emptystate__routepending.svg new file mode 100644 index 000000000000..7646917046cc --- /dev/null +++ b/assets/images/emptystate__routepending.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/expensify-app-icon.svg b/assets/images/expensify-app-icon.svg new file mode 100644 index 000000000000..a0adfe7dd952 --- /dev/null +++ b/assets/images/expensify-app-icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/assets/images/location.svg b/assets/images/location.svg new file mode 100644 index 000000000000..ad8102051e26 --- /dev/null +++ b/assets/images/location.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/assets/images/lounge-access.svg b/assets/images/lounge-access.svg deleted file mode 100644 index 3be9ff00fb7a..000000000000 --- a/assets/images/lounge-access.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/assets/images/new-expensify-adhoc.svg b/assets/images/new-expensify-adhoc.svg index db2f420c4e0e..26f18c8cc088 100644 --- a/assets/images/new-expensify-adhoc.svg +++ b/assets/images/new-expensify-adhoc.svg @@ -1,50 +1,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/new-expensify-dark.svg b/assets/images/new-expensify-dark.svg index 567cc667e972..bcdb3c87f164 100644 --- a/assets/images/new-expensify-dark.svg +++ b/assets/images/new-expensify-dark.svg @@ -1 +1,29 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/new-expensify-dev.svg b/assets/images/new-expensify-dev.svg index 5e36ffebe0d3..8f995412bb0c 100644 --- a/assets/images/new-expensify-dev.svg +++ b/assets/images/new-expensify-dev.svg @@ -1,46 +1,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/signIn/apple-logo.svg b/assets/images/signIn/apple-logo.svg new file mode 100644 index 000000000000..4e428fc41aed --- /dev/null +++ b/assets/images/signIn/apple-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/signIn/google-logo.svg b/assets/images/signIn/google-logo.svg new file mode 100644 index 000000000000..ebdd4be8cade --- /dev/null +++ b/assets/images/signIn/google-logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/contributingGuides/APPLE_GOOGLE_SIGNIN.md b/contributingGuides/APPLE_GOOGLE_SIGNIN.md new file mode 100644 index 000000000000..9032a99dfbbd --- /dev/null +++ b/contributingGuides/APPLE_GOOGLE_SIGNIN.md @@ -0,0 +1,272 @@ +# Overview + +"Sign in with Apple" and "Sign in with Google" are multi-platform sign-in methods. Both Apple and Google provide official tools, but we have to manage the fact that the behavior, APIs, and constraints for each of those tools varies quite a bit. The architecture of Apple and Google sign-in aims to provide as consistent a user experience and implementation as possible, but our options are limited by Apple and Google. This document will describe the user experience, tooling, and options available on each and why this feature is implemented the way it is. + +## Terms + +The **client app**, or **client**: this refers to the application that is attempting to access a user's resources hosted by a third party. In this case, this is the Expensify app. + +The **third party**: this is any other service that the client app (Expensify) wants to interact with on behalf of a user. In this case, Apple or Google. Since this flow is specifically concerned with authentication, it may also be called the **third-party authentication provider**. + +**Third-party sign-in**: a general phrase to refer to either "Sign in with Apple" or "Sign in with Google" (or any future similar features). Any authentication method that involves authentication with a service not provided by Expensify. + +## How third-party sign-in works + +When the user signs in to the app with a third party like Apple or Google, there is a general flow used by all of them: + +1. The user presses a button within the client app to start their preferred sign-in process. +2. The user is sent to a UI owned by the third-party to sign in (e.g., the Google sign-in web page hosted by Google, or the Sign in with Apple bottom sheet provided by iOS). +3. When the user successfully signs in with the third party, the third party generates a token and sends it back to the client app. +4. The client app sends the token to the client backend API, where the token is verified and the user's email is extracted from the token, and the user is signed in. + +Both services also require registering a "client ID", along with some configuration we'll explain next. For apps that aren't built using XCode, Apple calls this a "service ID", and it can be configured under "[Services IDs](https://developer.apple.com/account/resources/identifiers/list/serviceId)" in "Certificates, Identifiers & Profiles" in the Apple Developer console. (For apps made using XCode, like the iOS app, the bundle identifier is used as the client ID.) For Google, this configuration is done under "[Credentials](https://console.cloud.google.com/apis/credentials)" in the Google Cloud console. + +### On web + +We'll cover web details first, because web is treated as the "general use" case for services like this, and then platform-specific tools are built on top of that, which we'll cover afterwards. + +Both services also provide official Javascript libraries for integrating their services on web platforms. Using these libraries offers improved security and decreased maintenance burden over using the APIs directly, as Google notes while they heavily discourage using their auth APIs directly; but they also add additional constraints, which will be described later in the document. + +How the third party sends the token in step 3 depends on the third party's implementation and the app's configuration. In both Apple and Google's case, there are two main modes: "pop-up", and "redirect". + +#### Redirect mode + +From the user's perspective, redirect mode will usually look like opening the third party's sign-in page in the same browser window, and then redirecting back to the client app in that window. But re-use of the same window isn't required. The key point is the redirection back to the client app, via the third-party sign-in form making an HTTPS request. + +In both the Google and Apple JS libraries, the request endpoint, found at the "redirect URI", must handle a POST request with form data in the body, which contains the token we need to send to the client back-end API. This pattern is not easily implemented with the existing single-page web app, and so we use the other mode: "pop-up mode". + +The redirect URI must match a URI in the Google or Apple client ID configuration. + +#### Pop-up mode + +Pop-up mode opens a pop-up window to show the third-party sign-in form. But it also changes how tokens are given to the client app. Instead of an HTTPS request, they are returned by the JS libraries in memory, either via a callback (Google) or a promise (Apple). + +Apple and Google both check that the client app is running on an allowed domain. The sign-in process will fail otherwise. Google allows localhost, but Apple does not, and so testing Apple in development environments requires hosting the client app on a domain that the Apple client ID (or "service ID", in Apple's case) has been configured with. + +In the case of Apple, sometimes it will silently fail at the very end of the sign-in process, where the only sign that something is wrong is that the pop-up fails to close. In this case, it's very likely that configuration mismatch is the issue. + +In addition, Apple will require a valid redirect URI be provided in the library's configuration, even though it is not used. + +### Considerations for non-web platforms + +For apps that aren't web-based, there are other options: + +Sign in with Google provides libraries on [Android](https://developers.google.com/identity/sign-in/android/start) and [iOS](https://developers.google.com/identity/sign-in/ios/start) to use that will authenticate the mobile app is who it says it is, via app signing. For React Native, we use the [react-native-google-signin](https://github.com/react-native-google-signin/google-signin) wrapper to use these libraries. + +The [iOS implementation for Sign in with Apple](https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple?language=objc) can also verify the app's bundle ID and the team who signed it. We use the [react-native-apple-authentication](https://github.com/invertase/react-native-apple-authentication) wrapper library for this. + +There is no official library for Sign in with Apple on Android, so it has to work with the web tooling; but Android can't meet the requirements of the official JS library. It isn't hosted on a domain, which is required for pop-up flow, and can't receive an HTTPS request, which is required for redirect flow with the official JS library. To deal with this, react-native-apple-authentication's implementation uses a webview on Android, which can intercept the redirect POST and pass the data directly to the react-native app. + +#### Issues with third-party sign-in and Electron + +These tools aren't built with Electron or similar desktop apps in mind, and that presents similar challenges as Sign in with Apple for Android: + +1. Like mobile platforms, Electron does not have the option of validating the origin of the client app authentication request using a registered HTTPS domain +2. Unlike many mobile platforms, there are not official tools for Electron or desktop apps in general. +3. Attempts to get Electron to work like web are either blocked by the third-party authentication provider, broken, or inadvisable. + +These are the specific issues we've seen: + +1. [Google stopped allowing its sign-in page to render inside embedded browser frameworks](https://security.googleblog.com/2019/04/better-protection-against-man-in-middle.html) such as Electron. This means we can't open the sign-in flow inside the an Electron window. However, opening the sign-in form in the user's default web browser did work. +2. On the other hand, opening the Sign in with Apple form in the user's default browser instead of Electron does _not_ work, and renders an Apple page with an empty body instead of the sign-in form. + +We decided to instead redirect the user to a dedicated page in the web app to sign in. Apple and Google each have their own routes, `/sign-in-with-apple` and `/sign-in-with-google`, where the user is shown another button to click to start the sign-in process on web (since it shows a pop-up, the user must click the button directly, otherwise the pop-up would be blocked). After signing in, the user will be shown a deep link prompt in the browser to open the desktop app, where they will be signed in using a short-lived token from the Expensify API. + +Due to Expensify's expectation that a user will be using the same account on web and desktop, we do not go through this process if the user was already signed in, but instead the web app prompts the user to go back to desktop again, which will also sign them in on the desktop app. + +## Additional design constraints + +### New Google web library limits button style choices + +The current Sign in with Google library for web [does not allow arbitrary customization of the sign-in button](https://developers.google.com/identity/gsi/web/guides/offerings#sign_in_with_google_button). (The recently deprecated version of the Sign in with Google for web did offer this capability.) + +This means the button is limited in design: there are no offline or hover states, and there can only be a white background for the button. We were able to get the official Apple button options to match, so we used the Google options as the starting point for the design. + +### Sign in with Apple does not allow `localhost` + +Unlike Google, Apple does not allow `localhost` as a domain to host a pop-up or redirect to. In order to test Sign in with Apple on web or desktop, this means we have to: + +1. Use SSH tunneling to host the app on an HTTPS domain +2. Create a test Apple Service ID configuration in the Apple developer console, to allow testing the sign-in flow from its start until the point Apple sends its token to the Expensify app. +3. Use token interception on Android to test the web and desktop sign-in flow from the point where the front-end Expensify app has received a token, until the point where the user is signed in to Expensify using that token. + +These steps are covered in more detail in the "testing" section below. + +# Testing Apple/Google sign-in + +Due to some technical constraints, Apple and Google sign-in may require additional configuration to be able to work in the development environment as expected. This document describes any additional steps for each platform. + +## Apple + +### iOS/Android + +The iOS and Android implementations do not require extra steps to test, aside from signing into an Apple account on the iOS device before being able to use Sign in with Apple. + +### Web and desktop + +#### Render the web Sign In with Apple button in development + +The Google Sign In button renders differently in development mode. To prevent confusion +for developers about a possible regression, we decided to not render third party buttons in +development mode. + +To show the Apple Sign In button in development mode, you can comment out the following code in the +LoginForm.js file: + +```js +if (CONFIG.ENVIRONMENT === CONST.ENVIRONMENT.DEV) { + return; +} +``` + +#### Port requirements + +The Sign in with Apple process will break after the user signs in if the pop-up process is not started from a page at an HTTPS domain registered with Apple. To fix this, you could make a new configuration with your own HTTPS domain, but then the Apple configuration won't match that of Expensify's backend. + +So to be able to test this, we have two parts: +1. Create a valid Sign in with Apple token using valid configuration for the Expensify app, by creating and intercepting one on Android +2. Host the development web app at an HTTPS domain using SSH tunneling, and in the web app use a custom Apple config with this HTTPS domain registered + +Requirements: +1. Authorization on an Apple Development account or team to create new Service IDs +2. An SSH tunneling tool that provides static HTTPS domains. [ngrok](https://ngrok.com) is a good choice that provides one static HTTPS domain for a free account. + +#### Generate the token to use + +**Note**: complete this step before changing other configuration to test Apple on web and desktop, as updating those will cause Android to stop working while the configuration is changed. + +On an Android build, alter the `AppleSignIn` component to log the token generated, instead of sending it to the Expensify API: + +```js +// .then((token) => Session.beginAppleSignIn(token)) + .then((token) => console.log("TOKEN: ", token)) +``` + +If you need to check that you received the correct data, check it on [jwt.io](https://jwt.io), which will decode it if it is a valid JWT token. It will also show when the token expires. + +Hardcode this token into `Session.beginAppleSignIn`, and but also verify a valid token was passed into the function, for example: + +``` +function beginAppleSignIn(idToken) { ++ // Show that a token was passed in, without logging the token, for privacy ++ window.alert(`ORIGINAL ID TOKEN LENGTH: ${idToken.length}`); ++ const hardcodedToken = '...'; + const {optimisticData, successData, failureData} = signInAttemptState(); ++ API.write('SignInWithApple', {idToken: hardcodedToken}, {optimisticData, successData, failureData}); +- API.write('SignInWithApple', {idToken}, {optimisticData, successData, failureData}); +} +``` + +#### Configure the SSH tunneling + +You can use any SSH tunneling service that allows you to configure custom subdomains so that we have a consistent address to use. We'll use ngrok in these examples, but ngrok requires a paid account for this. If you need a free option, try serveo.net. + +After you've set ngrok up to be able to run on your machine (requires configuring a key with the command line tool, instructions provided by the ngrok website after you create an account), test hosting the web app on a custom subdomain. This example assumes the development web app is running at `localhost:8082`: + +``` +ngrok http 8082 --host-header="localhost:8082" --subdomain=mysubdomain +``` + +The `--host-header` flag is there to avoid webpack errors with header validation. In addition, add `allowedHosts: 'all'` to the dev server config in `webpack.dev.js`: + +```js +devServer: { + ..., + allowedHosts: 'all', +} +``` + +#### Configure Apple Service ID + +Now that you have an HTTPS address to use, you can create an Apple Service ID configuration that will work with it. + +1. Create a new app ID on your Apple development team that can be used to test this, following the instructions [here](https://github.com/invertase/react-native-apple-authentication/blob/main/docs/INITIAL_SETUP.md). +2. Create a new service ID following the instructions [here](https://github.com/invertase/react-native-apple-authentication/blob/main/docs/ANDROID_EXTRA.md). For allowed domains, enter your SSH tunnel address (e.g., `https://mysubdomain.ngrok-free.app`), and for redirect URLs, just make up an endpoint, it's never actually invoked (e.g., `mysubdomain.ngrok-free.app/appleauth`). + +Notes: +- Depending on your Apple account configuration, you may need additional permissions to access some of the features described in the instructions above. +- While the Apple Sign In configuration requires a `clientId`, the Apple Developer console calls this a `Service ID`. + +Finally, edit `.env` to use your client (service) ID and redirect URL config: + +``` +ASI_CLIENTID_OVERRIDE=com.example.test +ASI_REDIRECTURI_OVERRIDE=https://mysubdomain.ngrok-free.app/appleauth +``` + +#### Run the app + +Remember that you will need to restart the web server if you make a change to the `.env` file. + +### Desktop + +Desktop will require the same configuration, with these additional steps: + +#### Configure web app URL in .env + +Add `NEW_EXPENSIFY_URL` to .env, and set it to the HTTPS URL where the web app can be found, for example: + +``` +NEW_EXPENSIFY_URL=https://subdomain.ngrok-free.app +``` + +This is required because the desktop app needs to know the address of the web app, and must open it at the HTTPS domain configured to work with Sign in with Apple. + +Note that changing this value to a domain that isn't configured for use with Expensify will cause Android to break, as it is still using the real client ID, but now has an incorrect value for `redirectURI`. + +#### Set Environment to something other than "Development" + +The DeepLinkWrapper component will not handle deep links in the development environment. To be able to test deep linking, you must set the environment to something other than "Development". + +Within the `.env` file, set `envName` to something other than "Development", for example: + +``` +envName=Staging +``` + +Alternatively, within the `DeepLinkWrapper/index.website.js` file you can set the `CONFIG.ENVIRONMENT` to something other than "Development". + +#### Handle deep links in dev on MacOS + +If developing on MacOS, the development desktop app can't handle deeplinks correctly. To be able to test deeplinking back to the app, follow these steps: + +1. Create a "real" build of the desktop app, which can handle deep links, open the build folder, and install the dmg there: + +``` +npm run desktop-build --publish=never +open desktop-build +# Then double-click "NewExpensify.dmg" in Finder window +``` + +2. Even with this build, the deep link may not be handled by the correct app, as the development Electron config seems to intercept it sometimes. To manage this, install [SwiftDefaultApps](https://github.com/Lord-Kamina/SwiftDefaultApps), which adds a preference pane that can be used to configure which app should handle deep links. + +## Google + +### Web + +#### Render the web Sign In with Google button in Development + +The Google Sign In button renders differently in development mode. To prevent confusion +for developers about a possible regression, we decided to not render third party buttons in +development mode. + +To show the Google Sign In button in development mode, you can comment out the following code in the +LoginForm.js file: + +```js +if (CONFIG.ENVIRONMENT === CONST.ENVIRONMENT.DEV) { + return; +} +``` + +#### Port requirements + +Google allows the web app to be hosted at localhost, but according to the +current Google console configuration for the Expensify client ID, it must be +hosted on port 8082. + +### Desktop + +#### Set Environment to something other than "Development" + +The DeepLinkWrapper component will not handle deep links in the development environment. To be able to test deep linking, you must set the environment to something other than "Development". diff --git a/contributingGuides/TS_STYLE.md b/contributingGuides/TS_STYLE.md index 6d8d4a446428..0d6774792c45 100644 --- a/contributingGuides/TS_STYLE.md +++ b/contributingGuides/TS_STYLE.md @@ -23,6 +23,7 @@ - [1.16 Reusable Types](#reusable-types) - [1.17 `.tsx`](#tsx) - [1.18 No inline prop types](#no-inline-prop-types) + - [1.19 Satisfies operator](#satisfies-operator) - [Exception to Rules](#exception-to-rules) - [Communication Items](#communication-items) - [Migration Guidelines](#migration-guidelines) @@ -101,7 +102,7 @@ type Foo = { -- [1.2](#d-ts-extension) **`d.ts` Extension**: Do not use `d.ts` file extension even when a file contains only type declarations. Only exception is the `global.d.ts` file in which third party packages can be modified using module augmentation. Refer to the [Communication Items](#communication-items) section to learn more about module augmentation. +- [1.2](#d-ts-extension) **`d.ts` Extension**: Do not use `d.ts` file extension even when a file contains only type declarations. Only exceptions are `src/types/global.d.ts` and `src/types/modules/*.d.ts` files in which third party packages can be modified using module augmentation. Refer to the [Communication Items](#communication-items) section to learn more about module augmentation. > Why? Type errors in `d.ts` files are not checked by TypeScript [^1]. @@ -358,7 +359,7 @@ type Foo = { -- [1.15](#file-organization) **File organization**: In modules with platform-specific implementations, create `types.ts` to define shared types. Import and use shared types in each platform specific files. +- [1.15](#file-organization) **File organization**: In modules with platform-specific implementations, create `types.ts` to define shared types. Import and use shared types in each platform specific files. Do not use [`satisfies` operator](#satisfies-operator) for platform-specific implementations, always define shared types that complies with all variants. > Why? To encourage consistent API across platform-specific implementations. If you're migrating module that doesn't have a default implement (i.e. `index.ts`, e.g. `getPlatform`), refer to [Migration Guidelines](#migration-guidelines) for further information. @@ -458,6 +459,34 @@ type Foo = { } ``` + + +- [1.19](#satisfies-operator) **Satisfies Operator**: Use the `satisfies` operator when you need to validate that the structure of an expression matches a specific type, without affecting the resulting type of the expression. + + > Why? TypeScript developers often want to ensure that an expression aligns with a certain type, but they also want to retain the most specific type possible for inference purposes. The `satisfies` operator assists in doing both. + + ```ts + // BAD + const sizingStyles = { + w50: { + width: '50%', + }, + mw100: { + maxWidth: '100%', + }, + } as const; + + // GOOD + const sizingStyles = { + w50: { + width: '50%', + }, + mw100: { + maxWidth: '100%', + }, + } satisfies Record; + ``` + ## Exception to Rules Most of the rules are enforced in ESLint or checked by TypeScript. If you think your particular situation warrants an exception, post the context in the `#expensify-open-source` Slack channel with your message prefixed with `TS EXCEPTION:`. The internal engineer assigned to the PR should be the one that approves each exception, however all discussion regarding granting exceptions should happen in the public channel instead of the GitHub PR page so that the TS migration team can access them easily. @@ -472,9 +501,11 @@ This rule will apply until the migration is done. After the migration, discussio - I think types definitions in a third party library is incomplete or incorrect -When the library indeed contains incorrect or missing type definitions and it cannot be updated, use module augmentation to correct them. All module augmentation code should be contained in `/src/global.d.ts`. +When the library indeed contains incorrect or missing type definitions and it cannot be updated, use module augmentation to correct them. All module augmentation code should be contained in `/src/types/modules/*.d.ts`, each library as a separate file. ```ts +// external-library-name.d.ts + declare module "external-library-name" { interface LibraryComponentProps { // Add or modify typings @@ -487,6 +518,8 @@ declare module "external-library-name" { > This section contains instructions that are applicable during the migration. +- 🚨 DO NOT write new code in TypeScript yet. The only time you write TypeScript code is when the file you're editing has already been migrated to TypeScript by the migration team. This guideline will be updated once it's time for new code to be written in TypeScript. If you're doing a major overhaul or refactoring of particular features or utilities of App and you believe it might be beneficial to migrate relevant code to TypeScript as part of the refactoring, please ask in the #expensify-open-source channel about it (and prefix your message with `TS ATTENTION:`). + - If you're migrating a module that doesn't have a default implementation (i.e. `index.ts`, e.g. `getPlatform`), convert `index.website.js` to `index.ts`. Without `index.ts`, TypeScript cannot get type information where the module is imported. - Deprecate the usage of `underscore`. Use vanilla methods from JS instead. Only use `lodash` when there is no easy vanilla alternative (eg. `lodashMerge`). eslint: [`no-restricted-imports`](https://eslint.org/docs/latest/rules/no-restricted-imports) diff --git a/desktop/main.js b/desktop/main.js index 3a153b4d13c5..b19bef060ba9 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -11,7 +11,7 @@ const CONFIG = require('../src/CONFIG').default; const CONST = require('../src/CONST').default; const Localize = require('../src/libs/Localize'); -const port = process.env.PORT || 8080; +const port = process.env.PORT || 8082; const {DESKTOP_SHORTCUT_ACCELERATOR} = CONST; app.setName('New Expensify'); diff --git a/docs/Card-Rev-Share-for-Approved-Partners.md b/docs/Card-Rev-Share-for-Approved-Partners.md new file mode 100644 index 000000000000..9b5647a004d3 --- /dev/null +++ b/docs/Card-Rev-Share-for-Approved-Partners.md @@ -0,0 +1,17 @@ +--- +title: Expensify Card revenue share for ExpensifyApproved! partners +description: Earn money when your clients adopt the Expensify Card +--- + + +# About +Start making more with us! We're thrilled to announce a new incentive for our US-based ExpensifyApproved! partner accountants. You can now earn additional income for your firm every time your client uses their Expensify Card. We're offering 0.5% of the total Expensify Card spend of your clients in cashback returned to your firm. The more your clients spend, the more cashback your firm receives!
+
This program is currently only available to US-based ExpensifyApproved! partner accountants. + +# How-to +To benefit from this program, all you need to do is ensure that you are listed as a domain admin on your client's Expensify account. If you're not currently a domain admin, your client can follow the instructions outlined in [our help article](https://community.expensify.com/discussion/5749/how-to-add-and-remove-domain-admins#:~:text=Domain%20Admins%20have%20total%20control,a%20member%20of%20the%20domain.) to assign you this role. +# FAQ +- What if my firm is not permitted to accept revenue share from our clients?
+
We understand that different firms may have different policies. If your firm is unable to accept this revenue share, you can pass the revenue share back to your client to give them an additional 0.5% of cash back using your own internal payment tools.

+- What if my firm does not wish to participate in the program?
+
Please reach out to your assigned partner manager at new.expensify.com to inform them you would not like to accept the revenue share nor do you want to pass the revenue share to your clients. diff --git a/docs/_includes/search-toggle.html b/docs/_includes/search-toggle.html new file mode 100644 index 000000000000..caa11c63c46f --- /dev/null +++ b/docs/_includes/search-toggle.html @@ -0,0 +1,5 @@ +

+ + + +
diff --git a/docs/_includes/sidebar-search.html b/docs/_includes/sidebar-search.html new file mode 100644 index 000000000000..a365c812e031 --- /dev/null +++ b/docs/_includes/sidebar-search.html @@ -0,0 +1,13 @@ + diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index e0ed71b46818..39d62bb0ea9c 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -2,7 +2,7 @@ - + Expensify Help @@ -12,12 +12,18 @@ + + + + {% seo %} - + + +
@@ -25,11 +31,20 @@
- - - +
+
+ + + +
+ + {% include search-toggle.html %} +
+ + {% include sidebar-search.html id="sidebar-layer" %} +
{% if page.url == "/" or page.url contains "/hubs/" %} diff --git a/docs/_sass/_colors.scss b/docs/_sass/_colors.scss index d9fa10d24b8e..b34a7d13b7f0 100644 --- a/docs/_sass/_colors.scss +++ b/docs/_sass/_colors.scss @@ -1,9 +1,14 @@ $color-green400: #03D47C; $color-green-icons: #8B9C8F; $color-green-borders: #1A3D32; +$color-button-background: #1A3D32; +$color-button-hovered: #2C6755; $color-green-highlightBG: #07271F; +$color-green-highlightBG-hover: #06231c; $color-green-appBG: #061B09; +$color-green-hover: #00a862; $color-light-gray-green: #AFBBB0; $color-blue300: #5AB0FF; $color-blue200: #B0D9FF; $color-white: #E7ECE9; +$color-gray-label: #afbbb0; diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index ebf96476bc0d..720bc95c0732 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -1,9 +1,11 @@ @import 'breakpoints'; @import 'colors'; @import 'fonts'; +@import 'search-bar'; $color-appBG: $color-green-appBG; $color-highlightBG: $color-green-highlightBG; +$color-accent : $color-green400; $color-borders: $color-green-borders; $color-icons: $color-green-icons; $color-text: $color-white; @@ -11,6 +13,8 @@ $color-link: $color-blue300; $color-link-hovered: $color-blue200; $color-success: $color-green400; $color-text-supporting: $color-light-gray-green; +$color-green-hover: $color-green-hover; +$color-gray-label: $color-gray-label; * { margin: 0; @@ -182,6 +186,18 @@ button { align-content: center; } +.flex { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -moz-flex; + display: -webkit-flex; + display: flex; + -webkit-flex-flow: row wrap; + flex-flow: row wrap; + align-content: space-between; +} + #lhn { position: fixed; background-color: $color-highlightBG; @@ -524,6 +540,7 @@ button { .base-icon { width: 20px; height: 20px; + cursor: pointer; } .homepage { diff --git a/docs/_sass/_search-bar.scss b/docs/_sass/_search-bar.scss new file mode 100644 index 000000000000..ce085878af46 --- /dev/null +++ b/docs/_sass/_search-bar.scss @@ -0,0 +1,270 @@ +@import 'breakpoints'; +@import 'colors'; +@import 'fonts'; + +$color-appBG: $color-green-appBG; +$color-highlightBG: $color-green-highlightBG; +$color-highlightBG-hover: $color-green-highlightBG-hover; +$color-accent : $color-green400; +$color-borders: $color-green-borders; +$color-icons: $color-green-icons; +$color-text: $color-white; +$color-link: $color-blue300; +$color-link-hovered: $color-blue200; +$color-success: $color-green400; +$color-text-supporting: $color-light-gray-green; +$color-green-hover: $color-green-hover; +$color-gray-label: $color-gray-label; + +.search-icon { + margin: auto 0px; +} + +#sidebar-search { + background-color: $color-appBG; + width: 375px; + height: 100vh; + position: fixed; + display: block; + top: 0; + right: 0; + z-index: 2; +} + +@media only screen and (max-width: $breakpoint-tablet) { + #sidebar-search { + width: 100%; + } +} + +.searchbar-title-wrapper { + padding: 20px; +} + +.search-title { + font-size: 17px; + padding-bottom: 20px; +} + +#toggle-search-close { + margin: auto; + margin-left: 0px; + margin-right: 10px; +} + +/* Sidebar Layer */ +#sidebar-layer { + position: fixed; + + /* Sit on top of the page content */ + display: none; + + /* Hidden by default */ + width: 100%; + + /* Full width (cover the whole page) */ + height: 100%; + + /* Full height (cover the whole page) */ + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.4); + z-index: 1; +} + +/* All gsc id & class are Google Search relate gcse_0 is the search bar parent & gcse_1 is the search result list parent */ +#___gcse_0 { + margin-left: 20px; +} + +/* This input is in #___gcse_0 search bar */ +input#gsc-i-id1.gsc-input { + background-color: $color-appBG; + color: #E7ECE9; + font-family: "ExpensifyNeue", "Segoe UI Emoji", "Noto Color Emoji" !important; +} + +/* These below #gsc-iw-id1, .gsc-input-box & .gsib_a are inner wrapper of search bar input */ +#gsc-iw-id1 { + background-color: $color-appBG; + border-bottom: $color-borders 2px solid; + border-bottom-left-radius: 0px; + + &:focus-within { + border-bottom: $color-accent 2px solid; + } +} + +.gsc-input-box .gsib_a { + padding: 5px 9px 4px 0px; +} + +.search-icon { + margin-left: auto; +} + +/* This is the close icon on search bar */ +.gsib_b .gsst_a .gscb_a { + color: $color-icons; + + &:hover { + color: $color-text; + } +} + +/* This is to manage hover on parent close icon and make it the same effect on close icon */ +.gsst_a:hover { + + .gscb_a { + color: $color-text !important; + } +} + +/* Manage Google Search label animation */ +input#gsc-i-id1:focus+label.search-label, +input#gsc-i-id1:valid+label.search-label, +input#gsc-i-id1:active+label.search-label { + transform: translateY(-100%) scale(0.80); +} + +label.search-label { + display: block; + position: absolute; + margin-top: -20px; + font-size: 15px; + font-family: "ExpensifyNeue", "Segoe UI Emoji", "Noto Color Emoji"; + transform: translateY(-50%); + left: 20px; + color: $color-gray-label; + transform-origin: left top; + user-select: none; + transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1), top 500ms; +} + +/* Hide the relevance, Ads, Branding, find more button & etc sections */ +.gsc-above-wrapper-area, +.gsc-webResult.gsc-result .gsc-url-top, +.gsc-results-wrapper-visible .gsc-adBlock, +.gcsc-more-maybe-branding-root, +.gcsc-find-more-on-google-root { + display: none; +} + +.gsc-control-cse { + background-color: $color-appBG; + border: $color-appBG; + font-family: "ExpensifyNeue", "Helvetica Neue", "Helvetica", Arial, sans-serif !important; + max-height: 80vh; + overflow-y: scroll; + -ms-overflow-style: none; + scrollbar-width: none; +} + +/* Hide the scrollbar */ +.gsc-control-cse::-webkit-scrollbar { + display: none; +} + +.gs-title { + font-weight: bold; +} + +/* Change the Google Search Button icon into Expensify icon button */ +.gsc-search-button.gsc-search-button-v2 { + padding: 10px; + margin-top: -7px; + margin-left: 15px; + margin-right: 20px; + border-radius: 25px; + background-color: $color-green400; + cursor: pointer; + width: 40px; + height: 40px; +} + +.gsc-search-button.gsc-search-button-v2:hover { + background-color: $color-green-hover; +} + +.gsc-search-button.gsc-search-button-v2 svg { + fill: $color-text; + height: auto; + width: auto; +} + +/* Change the path of the Google Search Button icon into Expensify icon */ +.gsc-search-button.gsc-search-button-v2 svg path { + d: path('M8 1c3.9 0 7 3.1 7 7 0 1.4-.4 2.7-1.1 3.8l5.2 5.2c.6.6.6 1.5 0 2.1-.6.6-1.5.6-2.1 0l-5.2-5.2C10.7 14.6 9.4 15 8 15c-3.9 0-7-3.1-7-7s3.1-7 7-7zm0 3c2.2 0 4 1.8 4 4s-1.8 4-4 4-4-1.8-4-4 1.8-4 4-4z'); + fill-rule: evenodd; + clip-rule: evenodd; +} + +.gsc-resultsbox-visible .gsc-webResult .gsc-result { + border-bottom: none; +} + + +/* Change Font the Google Search result */ +.gsc-control-cse .gsc-table-result { + font-family: "ExpensifyNeue", "Helvetica Neue", "Helvetica", Arial, sans-serif !important; +} + +/* Change Font result Paragraph color */ +.gsc-results .gs-webResult:not(.gs-no-results-result):not(.gs-error-result) .gs-snippet, .gs-fileFormatType { + color: $color-text; +} + + +/* Change the color of the Google Search Suggestion font */ +.gs-spelling.gs-result { + color: $color-text; +} + +/* Pagination related style */ +.gsc-resultsbox-visible .gsc-results .gsc-cursor-box { + text-align: center; +} + +.gsc-resultsbox-visible .gsc-results .gsc-cursor-box .gsc-cursor-page { + margin: 4px; + width: 28px; + height: 28px; + border-radius: 25px; + display: inline-block; + line-height: 2.5; + background-color: $color-accent; + font-weight: bold; + font-size: 11px; +} + + +/* Change the color & background of Google Search Pagination */ +.gsc-cursor-next-page, +.gsc-cursor-final-page { + color: $color-text; + background-color: $color-appBG; +} + +/* Change the color & background of Google Search Current Page */ +.gsc-resultsbox-visible .gsc-results .gsc-cursor-box .gsc-cursor-page.gsc-cursor-current-page { + background-color: $color-accent; + color: $color-text; + + &:hover { + text-decoration: none; + background-color: $color-accent; + } +} + +/* Change the color & background of Google Search of Other Page */ +.gsc-resultsbox-visible .gsc-results .gsc-cursor-box .gsc-cursor-page { + background-color: $color-button-background; + color: $color-text; + + &:hover { + background-color: $color-button-hovered; + text-decoration: none; + } +} diff --git a/docs/annotations.xml b/docs/annotations.xml new file mode 100644 index 000000000000..adb06b135f25 --- /dev/null +++ b/docs/annotations.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/articles/other/Card-Rev-Share-for-Approved-Partners.md b/docs/articles/other/Card-Rev-Share-for-Approved-Partners.md index 9b5647a004d3..44614d506d49 100644 --- a/docs/articles/other/Card-Rev-Share-for-Approved-Partners.md +++ b/docs/articles/other/Card-Rev-Share-for-Approved-Partners.md @@ -4,8 +4,7 @@ description: Earn money when your clients adopt the Expensify Card --- -# About -Start making more with us! We're thrilled to announce a new incentive for our US-based ExpensifyApproved! partner accountants. You can now earn additional income for your firm every time your client uses their Expensify Card. We're offering 0.5% of the total Expensify Card spend of your clients in cashback returned to your firm. The more your clients spend, the more cashback your firm receives!
+Start making more with us! We're thrilled to announce a new incentive for our US-based ExpensifyApproved! partner accountants. You can now earn additional income for your firm every time your client uses their Expensify Card. **In short, your firm gets 0.5% of your clients’ total Expensify Card spend as cash back**. The more your clients spend, the more cashback your firm receives!

This program is currently only available to US-based ExpensifyApproved! partner accountants. # How-to diff --git a/docs/articles/other/Enable-Location-Access-on-Web.md b/docs/articles/other/Enable-Location-Access-on-Web.md new file mode 100644 index 000000000000..6cc0d19e4cde --- /dev/null +++ b/docs/articles/other/Enable-Location-Access-on-Web.md @@ -0,0 +1,55 @@ +--- +title: Enable Location Access on Web +description: How to enable location access for Expensify websites on your browser +--- + + +# About + +If you'd like to use features that rely on your current location you will need to enable location permissions for Expensify. You can find instructions for how to enable location settings on the three most common web browsers below. If your browser is not in the list then please do a web search for your browser and "enable location settings". + +# How-to + + +### Chrome +1. Open Chrome +2. At the top right, click the three-dot Menu > Settings +3. Click "Privacy and Security" and then "Site Settings" +4. Click Location +5. Check the "Not allowed to see your location" list to make sure expensify.com and new.expensify.com are not listed. If they are, click the delete icon next to them to allow location access + +[Chrome help page](https://support.google.com/chrome/answer/142065) + +### Firefox + +1. Open Firefox +2. In the URL bar enter "about:preferences" +3. On the left hand side select "Privacy & Security" +4. Scroll down to Permissions +5. Click on Settings next to Location +6. If location access is blocked for expensify.com or new.expensify.com, you can update it here to allow access + +[Firefox help page](https://support.mozilla.org/en-US/kb/permissions-manager-give-ability-store-passwords-set-cookies-more) + +### Safari +1. In the top menu bar click Safari +2. Then select Settings > Websites +3. Click Location on the left hand side +4. If expensify.com or new.expensify.com have "Deny" set as their access, update it to "Ask" or "Allow" + +Ask: The site must ask if it can use your location. +Deny: The site can’t use your location. +Allow: The site can always use your location. + +[Safari help page](https://support.apple.com/guide/safari/websites-ibrwe2159f50/mac) diff --git a/docs/articles/other/Everything-About-Chat.md b/docs/articles/other/Everything-About-Chat.md index 5ab435eb0523..d52932daa5ff 100644 --- a/docs/articles/other/Everything-About-Chat.md +++ b/docs/articles/other/Everything-About-Chat.md @@ -65,3 +65,23 @@ You can find your display mode by clicking on your User Icon > Preferences > Pri If the person you want to chat with doesn’t appear in your contact list, simply type their email or phone number to invite them to chat! From there, they will receive an email with instructions and a link to create an account. Once they click the link, a new.expensify.com account is set up for them automatically (if they don't have one already), and they can start chatting with you immediately! + +## Flagging content as offensive +In order to maintain a safe community for our users, Expensify provides tools to report offensive content and unwanted behavior in Expensify Chat. If you see a message (or attachment/image) from another user that you’d like our moderators to review, you can flag it by clicking the flag icon in the message context menu (on desktop) or holding down on the message and selecting “Flag as offensive” (on mobile). + +![Moderation Context Menu](https://help.expensify.com/assets/images/moderation-context-menu.png){:width="100%"} + +Once the flag is selected, you will be asked to categorize the message (such as spam, bullying, and harassment). Select what you feel best represents the issue is with the content, and you’re done - the message will be sent off to our internal team for review. + +![Moderation Flagging Options](https://help.expensify.com/assets/images/moderation-flag-page.png){:width="100%"} + +Depending on the severity of the offense, messages can be hidden (with an option to reveal) or fully removed, and in extreme cases, the sender of the message can be temporarily or permanently blocked from posting. + +You will receive a whisper from Concierge any time your content has been flagged, as well as when you have successfully flagged a piece of content. + +![Moderation Reportee Whisper](https://help.expensify.com/assets/images/moderation-reportee-whisper.png){:width="100%"} +![Moderation Reporter Whisper](https://help.expensify.com/assets/images/moderation-reporter-whisper.png){:width="100%"} + +*Note: Any message sent in public chat rooms are automatically reviewed by an automated system looking for offensive content and sent to our moderators for final decisions if it is found.* + + diff --git a/docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md b/docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md index c5a06a3b5d3e..2c82c2d04273 100644 --- a/docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md +++ b/docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md @@ -97,7 +97,7 @@ We find chat to be a powerful way to not only engage your attendees, but direct - #social: Have your employees in this room sharing fun photos, stoking conversations, and responding to any questions or feedback. - Speaker rooms: Encourage your employees to jump in to comment on content and nudge attendees to engage with each other during sessions. -*Protip*: Expensify Chat has moderation tools to help flag comments deemed to be spam, inconsiderate, intimidating, bullying, harassment, assault. On any comment, just click the flag icon to moderate conversation. +*Protip*: Expensify Chat has [moderation tools](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) to help flag comments deemed to be spam, inconsiderate, intimidating, bullying, harassment, assault. On any comment, just click the flag icon to moderate conversation. ### Step 7: Follow up with attendees after the event diff --git a/docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md b/docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md index d7bdef860cf7..849932a33c2d 100644 --- a/docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md +++ b/docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md @@ -100,7 +100,7 @@ This is essentially like setting a daily or individual expense limitation on any *Receipt Required Amount: $75* Receipts are important, and in most cases you prefer an itemized receipt. However, Expensify will generate an IRS-compliant electronic receipt (not itemized) for every expense not tied to hotels expense. For this reason, it’s important to enforce a rule where anytime an employee is on the road, and making business-related purchases at hotel (which happens a lot!), they are required to attach a physical receipt. -![Expense Basics](https://help.expensify.com/assets/images/playbook-expense-basics.png) +![Expense Basics](https://help.expensify.com/assets/images/playbook-expense-basics.png){:width="100%"} At this point, you’ve set enough compliance controls around categorical spend and general expenses for all employees, such that you can put trust in our solution to audit all expenses up front so you don’t have to. Next, let’s dive into how we can comfortably take on more automation, while relying on compliance controls to capture bad behavior (or better yet, instill best practices in our employees). @@ -117,7 +117,7 @@ Between Expensify's SmartScan technology, automatic categorization, and [DoubleC Expenses with violations will stay behind for the employee to fix, while expenses that are “in-policy” will move into an approver’s queue to mitigate any potential for delays. Scheduled Submit will ensure all expenses are submitted automatically for approval. -![Scheduled submit](https://help.expensify.com/assets/images/playbook-scheduled-submit.png) +![Scheduled submit](https://help.expensify.com/assets/images/playbook-scheduled-submit.png){:width="100%"} > _We spent twice as much time and effort on expenses without getting nearly as accurate of results as with Expensify._ > @@ -151,7 +151,7 @@ We recommend you select *Advanced Approval* as your Approval Mode to set up a mi *Import your employees in bulk via CSV* Given the amount of employees you have, it’s best you import employees in bulk via CSV. You can learn more about using a CSV file to bulk upload employees with *Advanced Approval [here](https://community.expensify.com/discussion/5735/deep-dive-the-ins-and-outs-of-advanced-approval)* -![Bulk import your employees](https://help.expensify.com/assets/images/playbook-impoort-employees.png) +![Bulk import your employees](https://help.expensify.com/assets/images/playbook-impoort-employees.png){:width="100%"} *Manually Approve All Reports* In most cases, at this stage, approvers prefer to review all expenses for a few reasons. 1) We want to make sure expense coding is accurate, and 2) We want to make sure there are no bad actors before we export transactions to our accounting system. @@ -182,7 +182,7 @@ Expensify supports direct card feeds from most financial institutions. Setting u 3. Next, assign the corporate cards to your employees by selecting the employee’s email address and the corresponding card number from the two drop-down menus under the *Assign a Card* section 4. Set a transaction start date (this is really important to avoid pulling in multiple outdated historical expenses that you don’t want employees to submit) -![If you have existing corporate cards](https://help.expensify.com/assets/images/playbook-existing-corporate-card.png) +![If you have existing corporate cards](https://help.expensify.com/assets/images/playbook-existing-corporate-card.png){:width="100%"} As mentioned above, we’ll be able to pull in transactions as they post (daily) and handle receipt matching for you and your employees. One benefit of the Expensify Card for your company is being able to see transactions at the point of purchase which provides you with real-time compliance. We even send users push notifications to SmartScan their receipt when it’s required and generate IRS-compliant e-receipts as a backup wherever applicable. @@ -235,7 +235,7 @@ Similarly, you can send bills directly from Expensify as well. 3. At this point, you can also upload an attachment to further validate the bill if necessary 4. Click *Submit*, we’ll forward the newly created bill directly to your Supplier. -![Send bills directly from Expensify](https://help.expensify.com/assets/images/playbook-new-bill.png) +![Send bills directly from Expensify](https://help.expensify.com/assets/images/playbook-new-bill.png){:width="100%"} Reports, invoices, and bills are largely the same, in theory, just with different rules. As such, creating a customer invoice is just like creating an expense report and even a bill. @@ -258,7 +258,7 @@ We recommend reporting: - *Quarterly* - for budget comparison reporting. Pull up your BI tool and compare your active budgets with your spend reporting here in Expensify - *Annually* - Run annual spend trend reports with month-over-month spend analysis, and prepare yourself for the upcoming fiscal year. -![Expenses!](https://help.expensify.com/assets/images/playbook-expenses.png) +![Expenses!](https://help.expensify.com/assets/images/playbook-expenses.png){:width="100%"} ### Step 14: Set your Subscription Size and Add a Payment card Our pricing model is unique in the sense that you are in full control of your billing. Meaning, you have the ability to set a minimum number of employees you know will be active each month and you can choose which level of commitment fits best. We recommend setting your subscription to *Annual* to get an additional 50% off on your monthly Expensify bill. In the end, you've spent enough time getting your company fully set up with Expensify, and you've seen how well it supports you and your employees. Committing annually just makes sense. diff --git a/docs/assets/images/close.svg b/docs/assets/images/close.svg new file mode 100644 index 000000000000..71e4df7ace0c --- /dev/null +++ b/docs/assets/images/close.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/docs/assets/images/moderation-context-menu.png b/docs/assets/images/moderation-context-menu.png new file mode 100644 index 000000000000..55aa17498cf7 Binary files /dev/null and b/docs/assets/images/moderation-context-menu.png differ diff --git a/docs/assets/images/moderation-flag-page.png b/docs/assets/images/moderation-flag-page.png new file mode 100644 index 000000000000..e60e2ccb8776 Binary files /dev/null and b/docs/assets/images/moderation-flag-page.png differ diff --git a/docs/assets/images/moderation-reportee-whisper.png b/docs/assets/images/moderation-reportee-whisper.png new file mode 100644 index 000000000000..9235a262bef5 Binary files /dev/null and b/docs/assets/images/moderation-reportee-whisper.png differ diff --git a/docs/assets/images/moderation-reporter-whisper.png b/docs/assets/images/moderation-reporter-whisper.png new file mode 100644 index 000000000000..881b25268515 Binary files /dev/null and b/docs/assets/images/moderation-reporter-whisper.png differ diff --git a/docs/assets/images/search.svg b/docs/assets/images/search.svg new file mode 100644 index 000000000000..9680cc415454 --- /dev/null +++ b/docs/assets/images/search.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index d5d462b83e50..01ebb00b288c 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -75,9 +75,108 @@ function injectFooterCopywrite() { footer.innerHTML = `©2008-${new Date().getFullYear()} Expensify, Inc.`; } +function closeSidebar() { + document.getElementById('sidebar-layer').style.display = 'none'; + + // Make the body scrollable again + const body = document.body; + const scrollY = body.style.top; + + // Reset the position and top styles of the body element + body.style.position = ''; + body.style.top = ''; + + // Scroll to the original scroll position + window.scrollTo(0, parseInt(scrollY || '0', 10) * -1); +} + +function closeSidebarOnClickOutside(event) { + const sidebarLayer = document.getElementById('sidebar-layer'); + + if (event.target !== sidebarLayer) { + return; + } + closeSidebar(); +} + +function openSidebar() { + document.getElementById('sidebar-layer').style.display = 'block'; + + // Make body unscrollable + const yAxis = document.documentElement.style.getPropertyValue('y-axis'); + const body = document.body; + body.style.position = 'fixed'; + body.style.top = `-${yAxis}`; + + // Close the sidebar when clicking sidebar layer (outside the sidebar search) + const sidebarLayer = document.getElementById('sidebar-layer'); + if (sidebarLayer) { + sidebarLayer.addEventListener('click', closeSidebarOnClickOutside); + } +} + +// Function to adapt & fix cropped SVG viewBox from Google based on viewport (Mobile or Tablet-Desktop) +function changeSVGViewBoxGoogle() { + // Get all inline Google SVG elements on the page + const svgsGoogle = document.querySelectorAll('svg'); + + // Create a media query for screens wider than tablet + const mediaQuery = window.matchMedia('(min-width: 800px)'); + + // Check if the viewport is smaller than tablet + if (!mediaQuery.matches) { + Array.from(svgsGoogle).forEach((svg) => { + // Set the viewBox attribute to '0 0 13 13' to make the svg fit in the mobile view + svg.setAttribute('viewBox', '0 0 13 13'); + svg.setAttribute('height', '13'); + svg.setAttribute('width', '13'); + }); + } else { + Array.from(svgsGoogle).forEach((svg) => { + // Set the viewBox attribute to '0 0 20 20' to make the svg fit in the tablet-desktop view + svg.setAttribute('viewBox', '0 0 20 20'); + svg.setAttribute('height', '16'); + svg.setAttribute('width', '16'); + }); + } +} + +// Function to insert element after another +// In this case, we insert the label element after the Google Search Input so we can have the same label animation effect +function insertElementAfter(referenceNode, newNode) { + referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); +} + +// Need to wait up until page is load, so the svg viewBox can be changed +// And the search label can be inserted +window.addEventListener('load', () => { + changeSVGViewBoxGoogle(); + + // Add required into the search input + const searchInput = document.getElementById('gsc-i-id1'); + searchInput.setAttribute('required', ''); + + // Insert search label after the search input + const searchLabel = document.createElement('label'); + searchLabel.classList.add('search-label'); + searchLabel.innerHTML = 'Search for something...'; + insertElementAfter(searchInput, searchLabel); +}); + window.addEventListener('DOMContentLoaded', () => { injectFooterCopywrite(); + // Handle open & close the sidebar + const buttonOpenSidebar = document.getElementById('toggle-search-open'); + if (buttonOpenSidebar) { + buttonOpenSidebar.addEventListener('click', openSidebar); + } + + const buttonCloseSidebar = document.getElementById('toggle-search-close'); + if (buttonCloseSidebar) { + buttonCloseSidebar.addEventListener('click', closeSidebar); + } + if (window.tocbot) { window.tocbot.init({ // Where to render the table of contents. @@ -139,5 +238,8 @@ window.addEventListener('DOMContentLoaded', () => { const scrollingElement = e.target.scrollingElement; const scrollPercentageInArticleContent = clamp(scrollingElement.scrollTop - articleContent.offsetTop, 0, articleContent.scrollHeight) / articleContent.scrollHeight; lhnContent.scrollTop = scrollPercentageInArticleContent * lhnContent.scrollHeight; + + // Count property of y-axis to keep scroll position & reference it later for making the body fixed when sidebar opened + document.documentElement.style.setProperty('y-axis', `${window.scrollY}px`); }); }); diff --git a/docs/context.xml b/docs/context.xml new file mode 100644 index 000000000000..f62520153883 --- /dev/null +++ b/docs/context.xml @@ -0,0 +1,33 @@ + + + Expensify Help Search + Help Search configuration details + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 60d60934c2ba..92c61cb81b2c 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -22,7 +22,9 @@ platform :android do gradle( project_dir: './android', - task: ':app:assembleE2eRelease', + task: ':app:assemble', + flavor: 'e2e', + build_type: 'Release', ) end @@ -33,6 +35,7 @@ platform :android do gradle( project_dir: './android', task: 'assemble', + flavor: 'Production', build_type: 'Release', ) end @@ -44,7 +47,8 @@ platform :android do gradle( project_dir: './android', task: 'assemble', - build_type: 'InternalRelease', + flavor: 'Adhoc', + build_type: 'Release', ) aws_s3( @@ -67,13 +71,14 @@ platform :android do gradle( project_dir: './android', task: 'bundle', + flavor: 'Production', build_type: 'Release', ) upload_to_play_store( package_name: "com.expensify.chat", json_key: './android/app/android-fastlane-json-key.json', - aab: './android/app/build/outputs/bundle/release/app-release.aab', + aab: './android/app/build/outputs/bundle/productionRelease/app-production-release.aab', track: 'internal', rollout: '1.0' ) @@ -111,7 +116,7 @@ platform :ios do build_app( workspace: "./ios/NewExpensify.xcworkspace", - scheme: "NewExpensify" + scheme: "New Expensify" ) end @@ -138,19 +143,19 @@ platform :ios do ) install_provisioning_profile( - path: "./ios/chat_expensify_adhoc.mobileprovision" + path: "./ios/expensify_chat_adhoc.mobileprovision" ) build_app( workspace: "./ios/NewExpensify.xcworkspace", skip_profile_detection: true, - scheme: "NewExpensify", - xcargs: { :PROVISIONING_PROFILE_SPECIFIER => "chat_expensify_adhoc", }, + scheme: "New Expensify AdHoc", + xcargs: { :PROVISIONING_PROFILE_SPECIFIER => "expensify_chat_adhoc", }, export_method: "ad-hoc", export_options: { method: "ad-hoc", provisioningProfiles: { - "com.chat.expensify.chat" => "chat_expensify_adhoc", + "com.expensify.chat.adhoc" => "expensify_chat_adhoc", }, manageAppVersionAndBuildNumber: false } @@ -197,7 +202,8 @@ platform :ios do build_app( workspace: "./ios/NewExpensify.xcworkspace", - scheme: "NewExpensify", + scheme: "New Expensify", + output_name: "New Expensify.ipa", export_options: { manageAppVersionAndBuildNumber: false } diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 414ad71ab217..d87226269a8b 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -21,12 +21,12 @@ 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 */; }; + 34FF0B164B1D8ED1054BFBB6 /* libPods-NewExpensify-NewExpensifyTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FB387B20AE4E6E98858B6AA /* libPods-NewExpensify-NewExpensifyTests.a */; }; 374FB8D728A133FE000D84EF /* OriginImageRequestHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 374FB8D628A133FE000D84EF /* OriginImageRequestHandler.mm */; }; + 5A464BC8112CDB1DE1E38F1C /* libPods-NewExpensify.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AEFE6CD54912D427D19133C7 /* libPods-NewExpensify.a */; }; 7041848526A8E47D00E09F4D /* RCTStartupTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7041848426A8E47D00E09F4D /* RCTStartupTimer.m */; }; 7041848626A8E47D00E09F4D /* RCTStartupTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7041848426A8E47D00E09F4D /* RCTStartupTimer.m */; }; 70CF6E82262E297300711ADC /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70CF6E81262E297300711ADC /* BootSplash.storyboard */; }; - 9A65F0F374EC04ABB5FF40AF /* libPods-NewExpensify.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FD35F00FB84D9FCF60D56A7 /* libPods-NewExpensify.a */; }; - B54A7C3AA98189A600323C02 /* libPods-NewExpensify-NewExpensifyTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F679A86058F8C4B331D239C3 /* libPods-NewExpensify-NewExpensifyTests.a */; }; BDB853621F354EBB84E619C2 /* ExpensifyNewKansas-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */; }; DD79042B2792E76D004484B4 /* RCTBootSplash.m in Sources */ = {isa = PBXBuildFile; fileRef = DD79042A2792E76D004484B4 /* RCTBootSplash.m */; }; E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; @@ -50,41 +50,53 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00E356EE1AD99517003FC87E /* NewExpensifyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NewExpensifyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 02BE6CF80ED1BD2445267F92 /* Pods-NewExpensify.release development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.release development.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.release development.xcconfig"; sourceTree = ""; }; + 0B09CE5BDAF34DD3573AB4E2 /* Pods-NewExpensify.debug adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.debug adhoc.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.debug adhoc.xcconfig"; sourceTree = ""; }; 0CDA8E33287DD650004ECBEC /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = NewExpensify/AppDelegate.mm; sourceTree = ""; }; 0CDA8E36287DD6A0004ECBEC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = NewExpensify/Images.xcassets; sourceTree = ""; }; + 0E27AA27706D894246E7946D /* Pods-NewExpensify.debug production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.debug production.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.debug production.xcconfig"; sourceTree = ""; }; 0F5BE0CD252686320097D869 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 0F5E534E263B73D5004CA14F /* EnvironmentChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EnvironmentChecker.h; sourceTree = ""; }; 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EnvironmentChecker.m; sourceTree = ""; }; - 13B07F961A680F5B00A75B9A /* New Expensify.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "New Expensify.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07F961A680F5B00A75B9A /* New Expensify Dev.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "New Expensify Dev.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = NewExpensify/AppDelegate.h; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = NewExpensify/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = NewExpensify/main.m; sourceTree = ""; }; 18D050DF262400AF000D658B /* BridgingFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgingFile.swift; sourceTree = ""; }; - 2FD35F00FB84D9FCF60D56A7 /* libPods-NewExpensify.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NewExpensify.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DDE5449979A136852B939B5 /* Pods-NewExpensify.release adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.release adhoc.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.release adhoc.xcconfig"; sourceTree = ""; }; + 34A8FDD1F9AA58B8F15C8380 /* Pods-NewExpensify.release production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.release production.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.release production.xcconfig"; sourceTree = ""; }; 374FB8D528A133A7000D84EF /* OriginImageRequestHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OriginImageRequestHandler.h; path = NewExpensify/OriginImageRequestHandler.h; sourceTree = ""; }; 374FB8D628A133FE000D84EF /* OriginImageRequestHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = OriginImageRequestHandler.mm; path = NewExpensify/OriginImageRequestHandler.mm; sourceTree = ""; }; - 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 = ""; }; + 3D393D7ABC1092F1DE91397F /* Pods-NewExpensify-NewExpensifyTests.debug staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.debug staging.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.debug staging.xcconfig"; sourceTree = ""; }; + 432FF5842B766535509FC547 /* Pods-NewExpensify-NewExpensifyTests.debug adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.debug adhoc.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.debug adhoc.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 = ""; }; + 52796131E6554494B2DDB056 /* ExpensifyNeue-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Bold.otf"; path = "../assets/fonts/native/ExpensifyNeue-Bold.otf"; sourceTree = ""; }; + 6B5211DB0EEB46E12DF4AD2D /* Pods-NewExpensify-NewExpensifyTests.release adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.release adhoc.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.release adhoc.xcconfig"; sourceTree = ""; }; + 6BE16DA6EFF88513DB1CD47B /* Pods-NewExpensify-NewExpensifyTests.release staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.release staging.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.release staging.xcconfig"; sourceTree = ""; }; + 6FB387B20AE4E6E98858B6AA /* libPods-NewExpensify-NewExpensifyTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NewExpensify-NewExpensifyTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + 75CABB0D0ABB0082FE0EB600 /* Pods-NewExpensify.release staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.release staging.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.release staging.xcconfig"; sourceTree = ""; }; + 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-BoldItalic.otf"; path = "../assets/fonts/native/ExpensifyNeue-BoldItalic.otf"; sourceTree = ""; }; + 8D3B36BF88E773E3C1A383FA /* Pods-NewExpensify.debug staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.debug staging.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.debug staging.xcconfig"; sourceTree = ""; }; + 96552D489D9F09B6A5ABD81B /* Pods-NewExpensify-NewExpensifyTests.release production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.release production.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.release production.xcconfig"; sourceTree = ""; }; + AEFE6CD54912D427D19133C7 /* libPods-NewExpensify.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NewExpensify.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + BD6E1BA27D6ABE0AC9D70586 /* Pods-NewExpensify-NewExpensifyTests.release development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.release development.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.release development.xcconfig"; sourceTree = ""; }; + BF6A4C5167244B9FB8E4D4E3 /* ExpensifyNeue-Italic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Italic.otf"; path = "../assets/fonts/native/ExpensifyNeue-Italic.otf"; sourceTree = ""; }; + CECC4CBB97A55705A33BEA9E /* Pods-NewExpensify.debug development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.debug development.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.debug development.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 = ""; }; + DB76E0D5C670190A0997C71E /* Pods-NewExpensify-NewExpensifyTests.debug production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.debug production.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.debug production.xcconfig"; sourceTree = ""; }; + DCF33E34FFEC48128CDD41D4 /* ExpensifyMono-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; 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 = ""; }; + E2F1036F70CBFE39E9352674 /* Pods-NewExpensify-NewExpensifyTests.debug development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.debug development.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.debug development.xcconfig"; sourceTree = ""; }; + E704648954784DDFBAADF568 /* ExpensifyMono-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; 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; }; + F4F8A052A22040339996324B /* ExpensifyNeue-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Regular.otf"; path = "../assets/fonts/native/ExpensifyNeue-Regular.otf"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -92,7 +104,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B54A7C3AA98189A600323C02 /* libPods-NewExpensify-NewExpensifyTests.a in Frameworks */, + 34FF0B164B1D8ED1054BFBB6 /* libPods-NewExpensify-NewExpensifyTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -101,7 +113,7 @@ buildActionMask = 2147483647; files = ( E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, - 9A65F0F374EC04ABB5FF40AF /* libPods-NewExpensify.a in Frameworks */, + 5A464BC8112CDB1DE1E38F1C /* libPods-NewExpensify.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -147,8 +159,8 @@ children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, - F679A86058F8C4B331D239C3 /* libPods-NewExpensify-NewExpensifyTests.a */, - 2FD35F00FB84D9FCF60D56A7 /* libPods-NewExpensify.a */, + AEFE6CD54912D427D19133C7 /* libPods-NewExpensify.a */, + 6FB387B20AE4E6E98858B6AA /* libPods-NewExpensify-NewExpensifyTests.a */, ); name = Frameworks; sourceTree = ""; @@ -187,7 +199,7 @@ 83CBBA001A601CBA00E9B192 /* Products */ = { isa = PBXGroup; children = ( - 13B07F961A680F5B00A75B9A /* New Expensify.app */, + 13B07F961A680F5B00A75B9A /* New Expensify Dev.app */, 00E356EE1AD99517003FC87E /* NewExpensifyTests.xctest */, ); name = Products; @@ -211,10 +223,22 @@ EC29677F0A49C2946A495A33 /* Pods */ = { isa = PBXGroup; children = ( - 37F6DD6E91B4C55BD8DDC895 /* Pods-NewExpensify.debug.xcconfig */, - 391B5D1DB6CFBAC16FD11DC4 /* Pods-NewExpensify.release.xcconfig */, - B37C757CE02B734BFED38097 /* Pods-NewExpensify-NewExpensifyTests.debug.xcconfig */, - CA3A3642AEED7CF2D4CD3716 /* Pods-NewExpensify-NewExpensifyTests.release.xcconfig */, + CECC4CBB97A55705A33BEA9E /* Pods-NewExpensify.debug development.xcconfig */, + 0B09CE5BDAF34DD3573AB4E2 /* Pods-NewExpensify.debug adhoc.xcconfig */, + 8D3B36BF88E773E3C1A383FA /* Pods-NewExpensify.debug staging.xcconfig */, + 0E27AA27706D894246E7946D /* Pods-NewExpensify.debug production.xcconfig */, + 02BE6CF80ED1BD2445267F92 /* Pods-NewExpensify.release development.xcconfig */, + 1DDE5449979A136852B939B5 /* Pods-NewExpensify.release adhoc.xcconfig */, + 75CABB0D0ABB0082FE0EB600 /* Pods-NewExpensify.release staging.xcconfig */, + 34A8FDD1F9AA58B8F15C8380 /* Pods-NewExpensify.release production.xcconfig */, + E2F1036F70CBFE39E9352674 /* Pods-NewExpensify-NewExpensifyTests.debug development.xcconfig */, + 432FF5842B766535509FC547 /* Pods-NewExpensify-NewExpensifyTests.debug adhoc.xcconfig */, + 3D393D7ABC1092F1DE91397F /* Pods-NewExpensify-NewExpensifyTests.debug staging.xcconfig */, + DB76E0D5C670190A0997C71E /* Pods-NewExpensify-NewExpensifyTests.debug production.xcconfig */, + BD6E1BA27D6ABE0AC9D70586 /* Pods-NewExpensify-NewExpensifyTests.release development.xcconfig */, + 6B5211DB0EEB46E12DF4AD2D /* Pods-NewExpensify-NewExpensifyTests.release adhoc.xcconfig */, + 6BE16DA6EFF88513DB1CD47B /* Pods-NewExpensify-NewExpensifyTests.release staging.xcconfig */, + 96552D489D9F09B6A5ABD81B /* Pods-NewExpensify-NewExpensifyTests.release production.xcconfig */, ); path = Pods; sourceTree = ""; @@ -226,12 +250,12 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "NewExpensifyTests" */; buildPhases = ( - 534FB136F5054EABB5B78C45 /* [CP] Check Pods Manifest.lock */, + EA9511689FED50580B0F3DE7 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - 7026C9E151743F743B66758C /* [CP] Embed Pods Frameworks */, - D86F192E7E836C985A6C3887 /* [CP] Copy Pods Resources */, + 6B9E07408E1D6715FDAB0C98 /* [CP] Embed Pods Frameworks */, + DCBD600FEEE485201447211A /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -247,16 +271,16 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "NewExpensify" */; buildPhases = ( - 76D95BA26FBE16FB4160466A /* [CP] Check Pods Manifest.lock */, + 7E666D03089C35260C905B4A /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 0DD7756DAF1D223C57F4D186 /* [CP] Embed Pods Frameworks */, - 1243CB50053E5462B0B69043 /* [CP] Copy Pods Resources */, - BC051F7DE694DE52DA3FEB70 /* [CP-User] [RNFB] Core Configuration */, - AD029A24A9ACE21B4D2AE31D /* [CP-User] [RNFB] Crashlytics Configuration */, + 3792B4E76B24FC8F78B7FEB6 /* [CP] Embed Pods Frameworks */, + 5259EE1448507A682C02026F /* [CP] Copy Pods Resources */, + 5E34288ECB69FCFA24851234 /* [CP-User] [RNFB] Core Configuration */, + E10553ABAB7762D41AC85C09 /* [CP-User] [RNFB] Crashlytics Configuration */, ); buildRules = ( ); @@ -264,7 +288,7 @@ ); name = NewExpensify; productName = NewExpensify; - productReference = 13B07F961A680F5B00A75B9A /* New Expensify.app */; + productReference = 13B07F961A680F5B00A75B9A /* New Expensify Dev.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -277,6 +301,7 @@ TargetAttributes = { 00E356ED1AD99517003FC87E = { CreatedOnToolsVersion = 6.2; + DevelopmentTeam = 368M544MTT; ProvisioningStyle = Automatic; TestTargetID = 13B07F861A680F5B00A75B9A; }; @@ -352,15 +377,20 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $(pwd)/../main.jsbundle.map\"\n\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 0DD7756DAF1D223C57F4D186 /* [CP] Embed Pods Frameworks */ = { + 3792B4E76B24FC8F78B7FEB6 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-NewExpensify/Pods-NewExpensify-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/MapboxMaps/MapboxMaps.framework", + "${BUILT_PRODUCTS_DIR}/Turf/Turf.framework", "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion", "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCommon/MapboxCommon.framework/MapboxCommon", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCoreMaps/MapboxCoreMaps.framework/MapboxCoreMaps", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework/MapboxMobileEvents", "${PODS_XCFRAMEWORKS_BUILD_DIR}/Onfido/Onfido.framework/Onfido", "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", "${PODS_XCFRAMEWORKS_BUILD_DIR}/Plaid/LinkKit.framework/LinkKit", @@ -368,8 +398,13 @@ ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMaps.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Turf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCommon.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreMaps.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Onfido.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LinkKit.framework", @@ -380,7 +415,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NewExpensify/Pods-NewExpensify-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 1243CB50053E5462B0B69043 /* [CP] Copy Pods Resources */ = { + 5259EE1448507A682C02026F /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -392,6 +427,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipExtendedActionsResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipMessageCenterResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipPreferenceCenterResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", ); name = "[CP] Copy Pods Resources"; @@ -401,6 +437,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipExtendedActionsResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipMessageCenterResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipPreferenceCenterResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", ); runOnlyForDeploymentPostprocessing = 0; @@ -408,37 +445,33 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NewExpensify/Pods-NewExpensify-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 534FB136F5054EABB5B78C45 /* [CP] Check Pods Manifest.lock */ = { + 5E34288ECB69FCFA24851234 /* [CP-User] [RNFB] Core Configuration */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-NewExpensify-NewExpensifyTests-checkManifestLockResult.txt", + "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); + name = "[CP-User] [RNFB] Core Configuration"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n _JSON_OUTPUT_BASE64=$(python -c 'import json,sys,base64;print(base64.b64encode(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"').read())['${_JSON_ROOT}'])))' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; }; - 7026C9E151743F743B66758C /* [CP] Embed Pods Frameworks */ = { + 6B9E07408E1D6715FDAB0C98 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/MapboxMaps/MapboxMaps.framework", + "${BUILT_PRODUCTS_DIR}/Turf/Turf.framework", "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion", "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCommon/MapboxCommon.framework/MapboxCommon", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCoreMaps/MapboxCoreMaps.framework/MapboxCoreMaps", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework/MapboxMobileEvents", "${PODS_XCFRAMEWORKS_BUILD_DIR}/Onfido/Onfido.framework/Onfido", "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", "${PODS_XCFRAMEWORKS_BUILD_DIR}/Plaid/LinkKit.framework/LinkKit", @@ -446,8 +479,13 @@ ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMaps.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Turf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCommon.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreMaps.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Onfido.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LinkKit.framework", @@ -458,7 +496,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 76D95BA26FBE16FB4160466A /* [CP] Check Pods Manifest.lock */ = { + 7E666D03089C35260C905B4A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -480,59 +518,70 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - AD029A24A9ACE21B4D2AE31D /* [CP-User] [RNFB] Crashlytics Configuration */ = { + DCBD600FEEE485201447211A /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", - "$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + "${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipAutomationResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipCoreResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipExtendedActionsResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipMessageCenterResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipPreferenceCenterResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipAutomationResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipCoreResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipExtendedActionsResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipMessageCenterResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipPreferenceCenterResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", ); - name = "[CP-User] [RNFB] Crashlytics Configuration"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\nif [[ ${PODS_ROOT} ]]; then\n echo \"info: Exec FirebaseCrashlytics Run from Pods\"\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\nelse\n echo \"info: Exec FirebaseCrashlytics Run from framework\"\n \"${PROJECT_DIR}/FirebaseCrashlytics.framework/run\"\nfi\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-resources.sh\"\n"; + showEnvVarsInLog = 0; }; - BC051F7DE694DE52DA3FEB70 /* [CP-User] [RNFB] Core Configuration */ = { + E10553ABAB7762D41AC85C09 /* [CP-User] [RNFB] Crashlytics Configuration */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", + "$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); - name = "[CP-User] [RNFB] Core Configuration"; + name = "[CP-User] [RNFB] Crashlytics Configuration"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n _JSON_OUTPUT_BASE64=$(python -c 'import json,sys,base64;print(base64.b64encode(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"').read())['${_JSON_ROOT}'])))' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\nif [[ ${PODS_ROOT} ]]; then\n echo \"info: Exec FirebaseCrashlytics Run from Pods\"\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\nelse\n echo \"info: Exec FirebaseCrashlytics Run from framework\"\n \"${PROJECT_DIR}/FirebaseCrashlytics.framework/run\"\nfi\n"; }; - D86F192E7E836C985A6C3887 /* [CP] Copy Pods Resources */ = { + EA9511689FED50580B0F3DE7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipAutomationResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipCoreResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipExtendedActionsResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipMessageCenterResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipPreferenceCenterResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipAutomationResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipCoreResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipExtendedActionsResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipMessageCenterResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipPreferenceCenterResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + "$(DERIVED_FILE_DIR)/Pods-NewExpensify-NewExpensifyTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; FD10A7F022414F080027D42C /* Start Packager */ = { @@ -592,9 +641,9 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 00E356F61AD99517003FC87E /* Debug */ = { + 00E356F61AD99517003FC87E /* Debug Development */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B37C757CE02B734BFED38097 /* Pods-NewExpensify-NewExpensifyTests.debug.xcconfig */; + baseConfigurationReference = E2F1036F70CBFE39E9352674 /* Pods-NewExpensify-NewExpensifyTests.debug development.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -615,11 +664,11 @@ PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NewExpensify.app/NewExpensify"; }; - name = Debug; + name = "Debug Development"; }; - 00E356F71AD99517003FC87E /* Release */ = { + 00E356F71AD99517003FC87E /* Release Development */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CA3A3642AEED7CF2D4CD3716 /* Pods-NewExpensify-NewExpensifyTests.release.xcconfig */; + baseConfigurationReference = BD6E1BA27D6ABE0AC9D70586 /* Pods-NewExpensify-NewExpensifyTests.release development.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -638,13 +687,14 @@ PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NewExpensify.app/NewExpensify"; }; - name = Release; + name = "Release Development"; }; - 13B07F941A680F5B00A75B9A /* Debug */ = { + 13B07F941A680F5B00A75B9A /* Debug Development */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 37F6DD6E91B4C55BD8DDC895 /* Pods-NewExpensify.debug.xcconfig */; + baseConfigurationReference = CECC4CBB97A55705A33BEA9E /* Pods-NewExpensify.debug development.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDev; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; @@ -661,21 +711,22 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = com.chat.expensify.chat; - PRODUCT_NAME = "New Expensify"; - PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev; + PRODUCT_NAME = "New Expensify Dev"; + PROVISIONING_PROFILE_SPECIFIER = expensify_chat_dev; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; - name = Debug; + name = "Debug Development"; }; - 13B07F951A680F5B00A75B9A /* Release */ = { + 13B07F951A680F5B00A75B9A /* Release Development */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 391B5D1DB6CFBAC16FD11DC4 /* Pods-NewExpensify.release.xcconfig */; + baseConfigurationReference = 02BE6CF80ED1BD2445267F92 /* Pods-NewExpensify.release development.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDev; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; @@ -691,21 +742,267 @@ "-ObjC", "-lc++", ); + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev; + PRODUCT_NAME = "New Expensify Dev"; + PROVISIONING_PROFILE_SPECIFIER = expensify_chat_dev; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release Development"; + }; + 83CBBA201A601CBA00E9B192 /* Debug Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "$(inherited)"; + OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = ""; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + }; + name = "Debug Development"; + }; + 83CBBA211A601CBA00E9B192 /* Release Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "$(inherited)"; + OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = ""; + PRODUCT_NAME = ""; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = "Release Development"; + }; + CF9AF93E29EE9276001FA527 /* Debug Production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "$(inherited)"; + OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = ""; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + }; + name = "Debug Production"; + }; + CF9AF93F29EE9276001FA527 /* Debug Production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0E27AA27706D894246E7946D /* Pods-NewExpensify.debug production.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = 368M544MTT; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "$(SRCROOT)/NewExpensify/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); PRODUCT_BUNDLE_IDENTIFIER = com.chat.expensify.chat; PRODUCT_NAME = "New Expensify"; PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; - name = Release; + name = "Debug Production"; }; - 83CBBA201A601CBA00E9B192 /* Debug */ = { + CF9AF94029EE9276001FA527 /* Debug Production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DB76E0D5C670190A0997C71E /* Pods-NewExpensify-NewExpensifyTests.debug production.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = NewExpensifyTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NewExpensify.app/NewExpensify"; + }; + name = "Debug Production"; + }; + CF9AF94429EE927A001FA527 /* Debug AdHoc */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -759,17 +1056,75 @@ ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; }; - name = Debug; + name = "Debug AdHoc"; + }; + CF9AF94529EE927A001FA527 /* Debug AdHoc */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0B09CE5BDAF34DD3573AB4E2 /* Pods-NewExpensify.debug adhoc.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconAdHoc; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = 368M544MTT; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "$(SRCROOT)/NewExpensify/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.adhoc; + PRODUCT_NAME = "New Expensify AdHoc"; + PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug AdHoc"; }; - 83CBBA211A601CBA00E9B192 /* Release */ = { + CF9AF94629EE927A001FA527 /* Debug AdHoc */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 432FF5842B766535509FC547 /* Pods-NewExpensify-NewExpensifyTests.debug adhoc.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = NewExpensifyTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NewExpensify.app/NewExpensify"; + }; + name = "Debug AdHoc"; + }; + CF9AF94729EE928E001FA527 /* Release Production */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -815,11 +1170,178 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = ""; + PRODUCT_NAME = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; - name = Release; + name = "Release Production"; + }; + CF9AF94829EE928E001FA527 /* Release Production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 34A8FDD1F9AA58B8F15C8380 /* Pods-NewExpensify.release production.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = 368M544MTT; + INFOPLIST_FILE = NewExpensify/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.chat.expensify.chat; + PRODUCT_NAME = "New Expensify"; + PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release Production"; + }; + CF9AF94929EE928E001FA527 /* Release Production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 96552D489D9F09B6A5ABD81B /* Pods-NewExpensify-NewExpensifyTests.release production.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEVELOPMENT_TEAM = 368M544MTT; + INFOPLIST_FILE = NewExpensifyTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NewExpensify.app/NewExpensify"; + }; + name = "Release Production"; + }; + CF9AF94D29EE9293001FA527 /* Release AdHoc */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "$(inherited)"; + OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = ""; + PRODUCT_NAME = ""; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = "Release AdHoc"; + }; + CF9AF94E29EE9293001FA527 /* Release AdHoc */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1DDE5449979A136852B939B5 /* Pods-NewExpensify.release adhoc.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconAdHoc; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = NewExpensify/Chat.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = 368M544MTT; + INFOPLIST_FILE = NewExpensify/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.adhoc; + PRODUCT_NAME = "New Expensify AdHoc"; + PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release AdHoc"; + }; + CF9AF94F29EE9293001FA527 /* Release AdHoc */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6B5211DB0EEB46E12DF4AD2D /* Pods-NewExpensify-NewExpensifyTests.release adhoc.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEVELOPMENT_TEAM = 368M544MTT; + INFOPLIST_FILE = NewExpensifyTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NewExpensify.app/NewExpensify"; + }; + name = "Release AdHoc"; }; /* End XCBuildConfiguration section */ @@ -827,29 +1349,41 @@ 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "NewExpensifyTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 00E356F61AD99517003FC87E /* Debug */, - 00E356F71AD99517003FC87E /* Release */, + 00E356F61AD99517003FC87E /* Debug Development */, + CF9AF94629EE927A001FA527 /* Debug AdHoc */, + CF9AF94029EE9276001FA527 /* Debug Production */, + 00E356F71AD99517003FC87E /* Release Development */, + CF9AF94F29EE9293001FA527 /* Release AdHoc */, + CF9AF94929EE928E001FA527 /* Release Production */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Debug Development"; }; 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "NewExpensify" */ = { isa = XCConfigurationList; buildConfigurations = ( - 13B07F941A680F5B00A75B9A /* Debug */, - 13B07F951A680F5B00A75B9A /* Release */, + 13B07F941A680F5B00A75B9A /* Debug Development */, + CF9AF94529EE927A001FA527 /* Debug AdHoc */, + CF9AF93F29EE9276001FA527 /* Debug Production */, + 13B07F951A680F5B00A75B9A /* Release Development */, + CF9AF94E29EE9293001FA527 /* Release AdHoc */, + CF9AF94829EE928E001FA527 /* Release Production */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Debug Development"; }; 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "NewExpensify" */ = { isa = XCConfigurationList; buildConfigurations = ( - 83CBBA201A601CBA00E9B192 /* Debug */, - 83CBBA211A601CBA00E9B192 /* Release */, + 83CBBA201A601CBA00E9B192 /* Debug Development */, + CF9AF94429EE927A001FA527 /* Debug AdHoc */, + CF9AF93E29EE9276001FA527 /* Debug Production */, + 83CBBA211A601CBA00E9B192 /* Release Development */, + CF9AF94D29EE9293001FA527 /* Release AdHoc */, + CF9AF94729EE928E001FA527 /* Release Production */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Debug Development"; }; /* End XCConfigurationList section */ }; diff --git a/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/New Expensify AdHoc.xcscheme b/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/New Expensify AdHoc.xcscheme new file mode 100644 index 000000000000..0e0fad6399a0 --- /dev/null +++ b/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/New Expensify AdHoc.xcscheme @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/NewExpensify.xcscheme b/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/New Expensify Dev.xcscheme similarity index 88% rename from ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/NewExpensify.xcscheme rename to ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/New Expensify Dev.xcscheme index 87aa0146de0d..77f512242f67 100644 --- a/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/NewExpensify.xcscheme +++ b/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/New Expensify Dev.xcscheme @@ -15,7 +15,7 @@ @@ -23,7 +23,7 @@ @@ -41,7 +41,7 @@ + buildConfiguration = "Debug Development"> diff --git a/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/New Expensify.xcscheme b/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/New Expensify.xcscheme new file mode 100644 index 000000000000..f68be2705527 --- /dev/null +++ b/ios/NewExpensify.xcodeproj/xcshareddata/xcschemes/New Expensify.xcscheme @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/NewExpensify/Chat.entitlements b/ios/NewExpensify/Chat.entitlements index 33bb7f9feff8..5300e35eadbf 100644 --- a/ios/NewExpensify/Chat.entitlements +++ b/ios/NewExpensify/Chat.entitlements @@ -4,6 +4,10 @@ aps-environment development + com.apple.developer.applesignin + + Default + com.apple.developer.associated-domains applinks:new.expensify.com diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20@2x.png new file mode 100644 index 000000000000..b6f81e21850a Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20@2x~ipad.png new file mode 100644 index 000000000000..b6f81e21850a Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20@3x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20@3x.png new file mode 100644 index 000000000000..827df2594c05 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20~ipad.png new file mode 100644 index 000000000000..e15f81d06823 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-20~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29.png new file mode 100644 index 000000000000..c09f9e98e00e Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29@2x.png new file mode 100644 index 000000000000..6e8d7eb5977a Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29@2x~ipad.png new file mode 100644 index 000000000000..6e8d7eb5977a Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29@3x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29@3x.png new file mode 100644 index 000000000000..ea1de90cebb9 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29~ipad.png new file mode 100644 index 000000000000..c09f9e98e00e Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-29~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40@2x.png new file mode 100644 index 000000000000..405c9d06c2e7 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40@2x~ipad.png new file mode 100644 index 000000000000..405c9d06c2e7 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40@3x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40@3x.png new file mode 100644 index 000000000000..f7d677f601cd Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40~ipad.png new file mode 100644 index 000000000000..b6f81e21850a Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-40~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-60@2x~car.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-60@2x~car.png new file mode 100644 index 000000000000..f7d677f601cd Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-60@2x~car.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-60@3x~car.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-60@3x~car.png new file mode 100644 index 000000000000..ba5cbd6d0418 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-60@3x~car.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-83.5@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-83.5@2x~ipad.png new file mode 100644 index 000000000000..bc4a8fad1305 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon-83.5@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon@2x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon@2x.png new file mode 100644 index 000000000000..f7d677f601cd Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon@2x~ipad.png new file mode 100644 index 000000000000..3c4738b4e0d9 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon@3x.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon@3x.png new file mode 100644 index 000000000000..ba5cbd6d0418 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon~ios-marketing.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon~ios-marketing.png new file mode 100644 index 000000000000..ba9980fe553d Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon~ios-marketing.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon~ipad.png new file mode 100644 index 000000000000..d6902de513a8 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/AppIcon~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/Contents.json b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/Contents.json new file mode 100644 index 000000000000..bd04914aec96 --- /dev/null +++ b/ios/NewExpensify/Images.xcassets/AppIconAdHoc.appiconset/Contents.json @@ -0,0 +1,134 @@ +{ + "images": [ + { + "filename": "AppIcon@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "60x60" + }, + { + "filename": "AppIcon@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "60x60" + }, + { + "filename": "AppIcon~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "76x76" + }, + { + "filename": "AppIcon@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "76x76" + }, + { + "filename": "AppIcon-83.5@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "83.5x83.5" + }, + { + "filename": "AppIcon-40@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "40x40" + }, + { + "filename": "AppIcon-40@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "40x40" + }, + { + "filename": "AppIcon-40~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "40x40" + }, + { + "filename": "AppIcon-40@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "40x40" + }, + { + "filename": "AppIcon-20@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "20x20" + }, + { + "filename": "AppIcon-20@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "20x20" + }, + { + "filename": "AppIcon-20~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "20x20" + }, + { + "filename": "AppIcon-20@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "20x20" + }, + { + "filename": "AppIcon-29.png", + "idiom": "iphone", + "scale": "1x", + "size": "29x29" + }, + { + "filename": "AppIcon-29@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-29@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "29x29" + }, + { + "filename": "AppIcon-29~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "29x29" + }, + { + "filename": "AppIcon-29@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-60@2x~car.png", + "idiom": "car", + "scale": "2x", + "size": "60x60" + }, + { + "filename": "AppIcon-60@3x~car.png", + "idiom": "car", + "scale": "3x", + "size": "60x60" + }, + { + "filename": "AppIcon~ios-marketing.png", + "idiom": "ios-marketing", + "scale": "1x", + "size": "1024x1024" + } + ], + "info": { + "author": "iconkitchen", + "version": 1 + } +} \ No newline at end of file diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20@2x.png new file mode 100644 index 000000000000..827df9a2ad1f Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20@2x~ipad.png new file mode 100644 index 000000000000..827df9a2ad1f Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20@3x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20@3x.png new file mode 100644 index 000000000000..b7e326a153f0 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20~ipad.png new file mode 100644 index 000000000000..0b96abb2496d Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-20~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29.png new file mode 100644 index 000000000000..3a0648282861 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29@2x.png new file mode 100644 index 000000000000..a89052bf5818 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29@2x~ipad.png new file mode 100644 index 000000000000..a89052bf5818 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29@3x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29@3x.png new file mode 100644 index 000000000000..4234a1b8bc7d Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29~ipad.png new file mode 100644 index 000000000000..3a0648282861 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-29~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40@2x.png new file mode 100644 index 000000000000..535d2ea95841 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40@2x~ipad.png new file mode 100644 index 000000000000..535d2ea95841 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40@3x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40@3x.png new file mode 100644 index 000000000000..1ce8ff1c5a4e Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40~ipad.png new file mode 100644 index 000000000000..827df9a2ad1f Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-40~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-60@2x~car.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-60@2x~car.png new file mode 100644 index 000000000000..1ce8ff1c5a4e Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-60@2x~car.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-60@3x~car.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-60@3x~car.png new file mode 100644 index 000000000000..3306f28e9cfd Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-60@3x~car.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-83.5@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-83.5@2x~ipad.png new file mode 100644 index 000000000000..c92d9c97b673 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon-83.5@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon@2x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon@2x.png new file mode 100644 index 000000000000..1ce8ff1c5a4e Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon@2x~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon@2x~ipad.png new file mode 100644 index 000000000000..5ad52fc70033 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon@2x~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon@3x.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon@3x.png new file mode 100644 index 000000000000..3306f28e9cfd Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon~ios-marketing.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon~ios-marketing.png new file mode 100644 index 000000000000..431307ca66b4 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon~ios-marketing.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon~ipad.png b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon~ipad.png new file mode 100644 index 000000000000..9aff8b53fb0e Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/AppIcon~ipad.png differ diff --git a/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/Contents.json b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/Contents.json new file mode 100644 index 000000000000..bd04914aec96 --- /dev/null +++ b/ios/NewExpensify/Images.xcassets/AppIconDev.appiconset/Contents.json @@ -0,0 +1,134 @@ +{ + "images": [ + { + "filename": "AppIcon@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "60x60" + }, + { + "filename": "AppIcon@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "60x60" + }, + { + "filename": "AppIcon~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "76x76" + }, + { + "filename": "AppIcon@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "76x76" + }, + { + "filename": "AppIcon-83.5@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "83.5x83.5" + }, + { + "filename": "AppIcon-40@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "40x40" + }, + { + "filename": "AppIcon-40@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "40x40" + }, + { + "filename": "AppIcon-40~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "40x40" + }, + { + "filename": "AppIcon-40@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "40x40" + }, + { + "filename": "AppIcon-20@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "20x20" + }, + { + "filename": "AppIcon-20@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "20x20" + }, + { + "filename": "AppIcon-20~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "20x20" + }, + { + "filename": "AppIcon-20@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "20x20" + }, + { + "filename": "AppIcon-29.png", + "idiom": "iphone", + "scale": "1x", + "size": "29x29" + }, + { + "filename": "AppIcon-29@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-29@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "29x29" + }, + { + "filename": "AppIcon-29~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "29x29" + }, + { + "filename": "AppIcon-29@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-60@2x~car.png", + "idiom": "car", + "scale": "2x", + "size": "60x60" + }, + { + "filename": "AppIcon-60@3x~car.png", + "idiom": "car", + "scale": "3x", + "size": "60x60" + }, + { + "filename": "AppIcon~ios-marketing.png", + "idiom": "ios-marketing", + "scale": "1x", + "size": "1024x1024" + } + ], + "info": { + "author": "iconkitchen", + "version": 1 + } +} \ No newline at end of file diff --git a/ios/NewExpensify/Images.xcassets/BootSplashLogo.imageset/Contents.json b/ios/NewExpensify/Images.xcassets/BootSplashLogo.imageset/Contents.json index 570652dfdaa0..a8927aa86e2b 100644 --- a/ios/NewExpensify/Images.xcassets/BootSplashLogo.imageset/Contents.json +++ b/ios/NewExpensify/Images.xcassets/BootSplashLogo.imageset/Contents.json @@ -1,23 +1,23 @@ { - "images": [ + "images" : [ { - "idiom": "universal", - "filename": "bootsplash_logo.png", - "scale": "1x" + "filename" : "bootsplash_logo.png", + "idiom" : "universal", + "scale" : "1x" }, { - "idiom": "universal", - "filename": "bootsplash_logo@2x.png", - "scale": "2x" + "filename" : "bootsplash_logo@2x.png", + "idiom" : "universal", + "scale" : "2x" }, { - "idiom": "universal", - "filename": "bootsplash_logo@3x.png", - "scale": "3x" + "filename" : "bootsplash_logo@3x.png", + "idiom" : "universal", + "scale" : "3x" } ], - "info": { - "version": 1, - "author": "xcode" + "info" : { + "author" : "xcode", + "version" : 1 } } diff --git a/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/Contents.json b/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/Contents.json new file mode 100644 index 000000000000..a8927aa86e2b --- /dev/null +++ b/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "bootsplash_logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "bootsplash_logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "bootsplash_logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/bootsplash_logo.png b/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/bootsplash_logo.png new file mode 100644 index 000000000000..8fbef1c5ab06 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/bootsplash_logo.png differ diff --git a/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/bootsplash_logo@2x.png b/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/bootsplash_logo@2x.png new file mode 100644 index 000000000000..186a2f85e1dd Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/bootsplash_logo@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/bootsplash_logo@3x.png b/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/bootsplash_logo@3x.png new file mode 100644 index 000000000000..e208d1e0f8ab Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/BootSplashLogoAdHoc.imageset/bootsplash_logo@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/Contents.json b/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/Contents.json new file mode 100644 index 000000000000..a8927aa86e2b --- /dev/null +++ b/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "bootsplash_logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "bootsplash_logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "bootsplash_logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/bootsplash_logo.png b/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/bootsplash_logo.png new file mode 100644 index 000000000000..8fbef1c5ab06 Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/bootsplash_logo.png differ diff --git a/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/bootsplash_logo@2x.png b/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/bootsplash_logo@2x.png new file mode 100644 index 000000000000..186a2f85e1dd Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/bootsplash_logo@2x.png differ diff --git a/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/bootsplash_logo@3x.png b/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/bootsplash_logo@3x.png new file mode 100644 index 000000000000..e208d1e0f8ab Binary files /dev/null and b/ios/NewExpensify/Images.xcassets/BootSplashLogoDev.imageset/bootsplash_logo@3x.png differ diff --git a/ios/NewExpensify/Images.xcassets/Contents.json b/ios/NewExpensify/Images.xcassets/Contents.json index 2d92bd53fdb2..73c00596a7fc 100644 --- a/ios/NewExpensify/Images.xcassets/Contents.json +++ b/ios/NewExpensify/Images.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } } diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 4c769a9f1bbd..66186890d68f 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.53 + 1.3.57 CFBundleSignature ???? CFBundleURLTypes @@ -30,9 +30,17 @@ new-expensify + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3 + + CFBundleVersion - 1.3.53.1 + 1.3.57.3 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index eb8e731a3b94..d74d2f154b38 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.53 + 1.3.57 CFBundleSignature ???? CFBundleVersion - 1.3.53.1 + 1.3.57.3 diff --git a/ios/Podfile b/ios/Podfile index 3261d68fd27d..6445685db014 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,3 +1,7 @@ +# Set the type of Mapbox SDK to use +# This value is used by $RNMapboxMaps +$RNMapboxMapsImpl = 'mapbox' + # Resolve react_native_pods.rb with node to allow for hoisting require Pod::Executable.execute_command('node', ['-p', 'require.resolve( @@ -18,7 +22,7 @@ prepare_react_native_project! # dependencies: { # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), # ``` -flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled +flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled(['Debug Production', 'Debug Development', 'Debug AdHoc']) linkage = ENV['USE_FRAMEWORKS'] if linkage != nil @@ -41,9 +45,22 @@ def __apply_Xcode_14_3_RC_post_install_workaround(installer) end end +# Configure Mapbox before installing dependencies +pre_install do |installer| + $RNMapboxMaps.pre_install(installer) +end + target 'NewExpensify' do permissions_path = '../node_modules/react-native-permissions/ios' + project 'NewExpensify', + 'Debug Development' => :debug, + 'Debug AdHoc' => :debug, + 'Debug Production' => :debug, + 'Release Development' => :release, + 'Release AdHoc' => :release, + 'Release Production' => :release + pod 'Permission-LocationAccuracy', :path => "#{permissions_path}/LocationAccuracy" pod 'Permission-LocationAlways', :path => "#{permissions_path}/LocationAlways" pod 'Permission-LocationWhenInUse', :path => "#{permissions_path}/LocationWhenInUse" @@ -75,6 +92,9 @@ target 'NewExpensify' do end post_install do |installer| + # Configure Mapbox after installation + $RNMapboxMaps.post_install(installer) + # https://github.com/facebook/react-native/blob/main/scripts/react_native_pods.rb#L197-L202 react_native_post_install( installer, diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a486465b0a29..16ed1e05dc64 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -20,7 +20,15 @@ PODS: - Airship (= 16.11.3) - Airship/MessageCenter (= 16.11.3) - Airship/PreferenceCenter (= 16.11.3) + - AppAuth (1.6.2): + - AppAuth/Core (= 1.6.2) + - AppAuth/ExternalUserAgent (= 1.6.2) + - AppAuth/Core (1.6.2) + - AppAuth/ExternalUserAgent (1.6.2): + - AppAuth/Core - boost (1.76.0) + - BVLinearGradient (2.8.1): + - React-Core - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - FBLazyVector (0.72.3) @@ -184,6 +192,10 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) + - GoogleSignIn (7.0.0): + - AppAuth (~> 1.5) + - GTMAppAuth (< 3.0, >= 1.3) + - GTMSessionFetcher/Core (< 4.0, >= 1.1) - GoogleUtilities/AppDelegateSwizzler (7.11.1): - GoogleUtilities/Environment - GoogleUtilities/Logger @@ -204,6 +216,10 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (7.11.1): - GoogleUtilities/Logger + - GTMAppAuth (2.0.0): + - AppAuth/Core (~> 1.6) + - GTMSessionFetcher/Core (< 4.0, >= 1.5) + - GTMSessionFetcher/Core (3.1.1) - hermes-engine (0.72.3): - hermes-engine/Pre-built (= 0.72.3) - hermes-engine/Pre-built (0.72.3) @@ -221,6 +237,15 @@ PODS: - lottie-react-native (5.1.6): - lottie-ios (~> 3.4.0) - React-Core + - MapboxCommon (23.6.0) + - MapboxCoreMaps (10.14.0): + - MapboxCommon (~> 23.6) + - MapboxMaps (10.14.0): + - MapboxCommon (= 23.6.0) + - MapboxCoreMaps (= 10.14.0) + - MapboxMobileEvents (= 1.0.10) + - Turf (~> 2.0) + - MapboxMobileEvents (1.0.10) - nanopb (2.30908.0): - nanopb/decode (= 2.30908.0) - nanopb/encode (= 2.30908.0) @@ -699,6 +724,8 @@ PODS: - React-jsi (= 0.72.3) - React-logger (= 0.72.3) - React-perflogger (= 0.72.3) + - RNAppleAuthentication (2.2.2): + - React-Core - RNCAsyncStorage (1.17.11): - React-Core - RNCClipboard (1.5.1): @@ -736,8 +763,22 @@ PODS: - React-Core - RNGestureHandler (2.12.0): - React-Core + - RNGoogleSignin (10.0.1): + - GoogleSignIn (~> 7.0) + - React-Core - RNLocalize (2.2.6): - React-Core + - rnmapbox-maps (10.0.11): + - MapboxMaps (~> 10.14.0) + - React + - React-Core + - rnmapbox-maps/DynamicLibrary (= 10.0.11) + - Turf + - rnmapbox-maps/DynamicLibrary (10.0.11): + - MapboxMaps (~> 10.14.0) + - React + - React-Core + - Turf - RNPermissions (3.6.1): - React-Core - RNReactNativeHapticFeedback (1.14.0): @@ -783,6 +824,7 @@ PODS: - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) - SocketRocket (0.6.1) + - Turf (2.6.1) - VisionCamera (2.15.4): - React - React-callinvoker @@ -793,6 +835,7 @@ PODS: DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) + - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) @@ -879,6 +922,7 @@ DEPENDENCIES: - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - "RNAppleAuthentication (from `../node_modules/@invertase/react-native-apple-authentication`)" - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)" - "RNCPicker (from `../node_modules/@react-native-picker/picker`)" @@ -892,7 +936,9 @@ DEPENDENCIES: - "RNFBPerf (from `../node_modules/@react-native-firebase/perf`)" - RNFS (from `../node_modules/react-native-fs`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) + - "RNGoogleSignin (from `../node_modules/@react-native-google-signin/google-signin`)" - RNLocalize (from `../node_modules/react-native-localize`) + - "rnmapbox-maps (from `../node_modules/@rnmapbox/maps`)" - RNPermissions (from `../node_modules/react-native-permissions`) - RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`) - RNReanimated (from `../node_modules/react-native-reanimated`) @@ -905,6 +951,7 @@ SPEC REPOS: trunk: - Airship - AirshipFrameworkProxy + - AppAuth - CocoaAsyncSocket - Firebase - FirebaseABTesting @@ -926,10 +973,17 @@ SPEC REPOS: - fmt - GoogleAppMeasurement - GoogleDataTransport + - GoogleSignIn - GoogleUtilities + - GTMAppAuth + - GTMSessionFetcher - libevent - libwebp - lottie-ios + - MapboxCommon + - MapboxCoreMaps + - MapboxMaps + - MapboxMobileEvents - nanopb - Onfido - OpenSSL-Universal @@ -938,11 +992,14 @@ SPEC REPOS: - SDWebImage - SDWebImageWebPCoder - SocketRocket + - Turf - YogaKit EXTERNAL SOURCES: boost: :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" + BVLinearGradient: + :path: "../node_modules/react-native-linear-gradient" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" FBLazyVector: @@ -1068,6 +1125,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/utils" ReactCommon: :path: "../node_modules/react-native/ReactCommon" + RNAppleAuthentication: + :path: "../node_modules/@invertase/react-native-apple-authentication" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" RNCClipboard: @@ -1094,8 +1153,12 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-fs" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" + RNGoogleSignin: + :path: "../node_modules/@react-native-google-signin/google-signin" RNLocalize: :path: "../node_modules/react-native-localize" + rnmapbox-maps: + :path: "../node_modules/@rnmapbox/maps" RNPermissions: :path: "../node_modules/react-native-permissions" RNReactNativeHapticFeedback: @@ -1114,7 +1177,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Airship: c70eed50e429f97f5adb285423c7291fb7a032ae AirshipFrameworkProxy: 7bc4130c668c6c98e2d4c60fe4c9eb61a999be99 + AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 boost: 57d2868c099736d80fcd648bf211b4431e51a558 + BVLinearGradient: 421743791a59d259aec53f4c58793aad031da2ca CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 4cce221dd782d3ff7c4172167bba09d58af67ccb @@ -1140,12 +1205,19 @@ SPEC CHECKSUMS: glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91 GoogleDataTransport: f0308f5905a745f94fb91fea9c6cbaf3831cb1bd + GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842 GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749 + GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae + GTMSessionFetcher: e8647203b65cee28c5f73d0f473d096653945e72 hermes-engine: 10fbd3f62405c41ea07e71973ea61e1878d07322 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef lottie-ios: 8f97d3271e155c2d688875c29cd3c74908aef5f8 lottie-react-native: 8f9d4be452e23f6e5ca0fdc11669dc99ab52be81 + MapboxCommon: 4a0251dd470ee37e7fadda8e285c01921a5e1eb0 + MapboxCoreMaps: eb07203bbb0b1509395db5ab89cd3ad6c2e3c04c + MapboxMaps: af50ec61a7eb3b032c3f7962c6bd671d93d2a209 + MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 Onfido: e36f284b865adcf99d9c905590a64ac09d4a576b onfido-react-native-sdk: 4ecde1a97435dcff9f00a878e3f8d1eb14fabbdc @@ -1207,6 +1279,7 @@ SPEC CHECKSUMS: React-runtimescheduler: 837c1bebd2f84572db17698cd702ceaf585b0d9a React-utils: bcb57da67eec2711f8b353f6e3d33bd8e4b2efa3 ReactCommon: 3ccb8fb14e6b3277e38c73b0ff5e4a1b8db017a9 + RNAppleAuthentication: 0571c08da8c327ae2afc0261b48b4a515b0286a6 RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60 RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495 RNCPicker: 0b65be85fe7954fbb2062ef079e3d1cde252d888 @@ -1220,7 +1293,9 @@ SPEC CHECKSUMS: RNFBPerf: 389914cda4000fe0d996a752532a591132cbf3f9 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: dec4645026e7401a0899f2846d864403478ff6a5 + RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 + rnmapbox-maps: 6f638ec002aa6e906a6f766d69cd45f968d98e64 RNPermissions: dcdb7b99796bbeda6975a6e79ad519c41b251b1c RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c RNReanimated: 020859659f64be2d30849a1fe88c821a7c3e0cbf @@ -1229,10 +1304,11 @@ SPEC CHECKSUMS: SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 + Turf: 469ce2c3d22e5e8e4818d5a3b254699a5c89efa4 VisionCamera: d3ec8883417a6a4a0e3a6ba37d81d22db7611601 Yoga: 8796b55dba14d7004f980b54bcc9833ee45b28ce YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: bc8161c6bfffeec6e6eaf84be18de5041ddcacf6 +PODFILE CHECKSUM: 845537d35601574adcd0794e17003ba7dbccdbfd COCOAPODS: 1.12.1 diff --git a/ios/tmp.xcconfig b/ios/tmp.xcconfig new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/ios/tmp.xcconfig @@ -0,0 +1 @@ + diff --git a/jest.config.js b/jest.config.js index 02597af9c9f2..1f540a679b9a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -22,7 +22,7 @@ module.exports = { doNotFake: ['nextTick'], }, testEnvironment: 'jsdom', - setupFiles: ['/jest/setup.js'], + setupFiles: ['/jest/setup.js', './node_modules/@react-native-google-signin/google-signin/jest/build/setup.js'], setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'], cacheDirectory: '/.jest-cache', }; diff --git a/package-lock.json b/package-lock.json index 75736f2c68ac..2b3df7229b67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.53-1", + "version": "1.3.57-3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.53-1", + "version": "1.3.57-3", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -17,6 +17,7 @@ "@formatjs/intl-numberformat": "^8.5.0", "@formatjs/intl-pluralrules": "^5.2.2", "@gorhom/portal": "^1.0.14", + "@invertase/react-native-apple-authentication": "^2.2.2", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "7.4.0", "@react-native-async-storage/async-storage": "^1.17.10", @@ -28,11 +29,13 @@ "@react-native-firebase/app": "^12.3.0", "@react-native-firebase/crashlytics": "^12.3.0", "@react-native-firebase/perf": "^12.3.0", + "@react-native-google-signin/google-signin": "^10.0.1", "@react-native-picker/picker": "^2.4.3", "@react-navigation/material-top-tabs": "^6.6.3", "@react-navigation/native": "6.1.6", "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", + "@rnmapbox/maps": "^10.0.11", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", "babel-plugin-transform-remove-console": "^6.9.4", @@ -42,14 +45,14 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#b60e464ca23e452eacffb93d471abed977b9abf0", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#4cc5f72b69bd77d2c8052a3c167d039e502a2796", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", + "idb-keyval": "^6.2.1", "jest-when": "^3.5.2", - "localforage": "^1.10.0", - "localforage-removeitems": "^1.4.0", "lodash": "4.17.21", "lottie-react-native": "^5.1.6", + "mapbox-gl": "^2.15.0", "metro-config": "^0.71.3", "moment": "^2.29.4", "moment-timezone": "^0.5.31", @@ -62,6 +65,7 @@ "react-collapse": "^5.1.0", "react-content-loader": "^6.1.0", "react-dom": "18.1.0", + "react-map-gl": "^7.1.3", "react-native": "0.72.3", "react-native-blob-util": "^0.17.3", "react-native-collapsible": "^1.6.0", @@ -78,9 +82,10 @@ "react-native-image-picker": "^5.1.0", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#8393b7e58df6ff65fd41f60aee8ece8822c91e2b", "react-native-key-command": "^1.0.1", + "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.59", + "react-native-onyx": "1.0.63", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.6.2", "react-native-performance": "^4.0.0", @@ -95,16 +100,19 @@ "react-native-screens": "3.21.0", "react-native-svg": "^13.9.0", "react-native-tab-view": "^3.5.2", + "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", "react-native-vision-camera": "^2.15.4", + "react-native-web-linear-gradient": "^1.1.2", "react-native-web-lottie": "^1.4.4", "react-native-webview": "^11.17.2", + "react-native-x-maps": "1.0.10", "react-pdf": "^6.2.2", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", "react-window": "^1.8.9", "save": "^2.4.0", - "semver": "^7.3.8", + "semver": "^7.5.2", "shim-keyboard-event-key": "^1.0.3", "underscore": "^1.13.1" }, @@ -143,6 +151,7 @@ "@types/jest-when": "^3.5.2", "@types/js-yaml": "^4.0.5", "@types/lodash": "^4.14.195", + "@types/mapbox-gl": "^2.7.13", "@types/mock-fs": "^4.13.1", "@types/pusher-js": "^5.1.0", "@types/react": "^18.2.12", @@ -184,7 +193,6 @@ "eslint-plugin-react-native-a11y": "^3.3.0", "eslint-plugin-storybook": "^0.5.13", "eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0", - "flipper-plugin-bridgespy-client": "^0.1.9", "html-webpack-plugin": "^5.5.0", "jest": "29.4.1", "jest-circus": "29.4.1", @@ -207,7 +215,7 @@ "style-loader": "^2.0.0", "time-analytics-webpack-plugin": "^0.1.17", "type-fest": "^3.12.0", - "typescript": "^4.8.4", + "typescript": "^5.1.6", "wait-port": "^0.2.9", "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.5.0", @@ -3967,6 +3975,11 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@invertase/react-native-apple-authentication": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@invertase/react-native-apple-authentication/-/react-native-apple-authentication-2.2.2.tgz", + "integrity": "sha512-uNZcUn9WbAQP5zSOFXI1+kEUokLwZG9imUulFdt5t22CU2ozGq6zyPm+BAVVg8D5eUUXduX/dJFhbuOpJxiEhQ==" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -5449,6 +5462,31 @@ "node": ">= 10.0.0" } }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-supported": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", + "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==" + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", @@ -5516,6 +5554,55 @@ "node": ">=6" } }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.0.tgz", + "integrity": "sha512-ZbhX9CTV+Z7vHwkRIasDOwTSzr76e8Q6a55RMsAibjyX6+P0ZNL1qAKNzOjjBDP3+aEfNMl7hHo5knuY6pTAUQ==", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, "node_modules/@mdx-js/mdx": { "version": "1.6.22", "dev": true, @@ -8509,6 +8596,21 @@ "@react-native-firebase/app": "12.9.3" } }, + "node_modules/@react-native-google-signin/google-signin": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@react-native-google-signin/google-signin/-/google-signin-10.0.1.tgz", + "integrity": "sha512-oZoU2lfKyn0s0GqqdFsi4v2FSENrxQYQU9DD/RSkxDdkIQ49Wwo6p5LKlgXY04GwZEVdYMuvZN3G89gQW0ig2g==", + "peerDependencies": { + "expo": ">=47.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, "node_modules/@react-native-picker/picker": { "version": "2.4.4", "license": "MIT", @@ -9415,6 +9517,34 @@ "loose-envify": "^1.1.0" } }, + "node_modules/@rnmapbox/maps": { + "version": "10.0.11", + "resolved": "https://registry.npmjs.org/@rnmapbox/maps/-/maps-10.0.11.tgz", + "integrity": "sha512-CqaAOEV2nYjZzAwSd7RceGIVVIyDO0G/Vqdvgen20LDuejX9N9Yqw7BrMH8MgIH3FNFxtjwyXiw6aVtybpke0w==", + "dependencies": { + "@turf/along": "6.5.0", + "@turf/distance": "6.5.0", + "@turf/helpers": "6.5.0", + "@turf/length": "6.5.0", + "@turf/nearest-point-on-line": "6.5.0", + "@types/geojson": "^7946.0.7", + "debounce": "^1.2.0" + }, + "peerDependencies": { + "expo": ">=47.0.0", + "mapbox-gl": "^2.9.0", + "react": ">=16.6.1", + "react-native": ">=0.59.9" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "mapbox-gl": { + "optional": true + } + } + }, "node_modules/@sentry/browser": { "version": "7.11.1", "license": "BSD-3-Clause", @@ -19749,6 +19879,157 @@ "node": ">=10.13.0" } }, + "node_modules/@turf/along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz", + "integrity": "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", + "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.5.0.tgz", + "integrity": "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz", + "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz", + "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==", + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/length": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/length/-/length-6.5.0.tgz", + "integrity": "sha512-5pL5/pnw52fck3oRsHDcSGrj9HibvtlrZ0QNy2OcW8qBFDNgZ4jtl6U7eATVoyWPKBHszW3dWETW+iLV7UARig==", + "dependencies": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.5.0.tgz", + "integrity": "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-rbush": "3.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-segment": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.5.0.tgz", + "integrity": "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz", + "integrity": "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@types/acorn": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", @@ -19937,6 +20218,11 @@ "@types/node": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" + }, "node_modules/@types/glob": { "version": "7.2.0", "dev": true, @@ -20092,6 +20378,14 @@ "version": "4.0.2", "license": "MIT" }, + "node_modules/@types/mapbox-gl": { + "version": "2.7.13", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.13.tgz", + "integrity": "sha512-qNffhTdYkeFl8QG9Q1zPPJmcs8PvHgmLa1PcwP1rxvcfMsIgcFr/FnrCttG0ZnH7Kzdd7xfECSRNTWSr4jC3PQ==", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/mdast": { "version": "3.0.10", "dev": true, @@ -21988,7 +22282,6 @@ }, "node_modules/arr-union": { "version": "3.1.0", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -22181,7 +22474,6 @@ }, "node_modules/assign-symbols": { "version": "1.0.0", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -23874,6 +24166,23 @@ "node": ">= 0.8" } }, + "node_modules/bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "dependencies": { + "typewise-core": "^1.2" + } + }, "node_modules/c8": { "version": "7.12.0", "dev": true, @@ -24924,6 +25233,19 @@ "typescript": "^4.0.2" } }, + "node_modules/config-file-ts/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "dev": true, @@ -25931,6 +26253,11 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==" + }, "node_modules/cssesc": { "version": "3.0.0", "dev": true, @@ -26041,6 +26368,11 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "node_modules/debug": { "version": "4.3.4", "license": "MIT", @@ -26774,6 +27106,11 @@ "stream-shift": "^1.0.0" } }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -29206,8 +29543,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#b60e464ca23e452eacffb93d471abed977b9abf0", - "integrity": "sha512-SA+1PDrST90MoWKNuqyfw7vT1c3S14JrrHCuk5l5m77k2T1Khu1lHPAw7sCUt0Yeoceq7JHL7zC4ZPhqVzDXwQ==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#4cc5f72b69bd77d2c8052a3c167d039e502a2796", + "integrity": "sha512-nNYAweSE5bwjKyFTi9tz+p1z+gxkytCnIa8M11vnseV60ZzJespcwB/2SbWkdaAL5wpvcgHLlFTTGbPUwIiTvw==", "license": "MIT", "dependencies": { "classnames": "2.3.1", @@ -29389,7 +29726,6 @@ }, "node_modules/extend-shallow": { "version": "3.0.2", - "devOptional": true, "license": "MIT", "dependencies": { "assign-symbols": "^1.0.0", @@ -29979,16 +30315,6 @@ "dev": true, "license": "ISC" }, - "node_modules/flipper-plugin-bridgespy-client": { - "version": "0.1.9", - "dev": true, - "license": "MIT", - "peerDependencies": { - "react-native": ">=0.62.0", - "react-native-flipper": ">=0.54.0 <1.0.0", - "typescript": ">=3.5.0 <5.0.0" - } - }, "node_modules/flow-enums-runtime": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.5.tgz", @@ -30397,6 +30723,28 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-rbush": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.2.0.tgz", + "integrity": "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==", + "dependencies": { + "@turf/bbox": "*", + "@turf/helpers": "6.x", + "@turf/meta": "6.x", + "@types/geojson": "7946.0.8", + "rbush": "^3.0.1" + } + }, + "node_modules/geojson-rbush/node_modules/@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + }, + "node_modules/geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, "node_modules/get-caller-file": { "version": "2.0.5", "license": "ISC", @@ -30466,7 +30814,6 @@ }, "node_modules/get-value": { "version": "2.0.6", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -30485,6 +30832,11 @@ "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", "dev": true }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "node_modules/glob": { "version": "7.1.6", "license": "ISC", @@ -30639,6 +30991,11 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/grid-index": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", + "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==" + }, "node_modules/gzip-size": { "version": "6.0.0", "dev": true, @@ -31787,6 +32144,11 @@ "node": ">= 6" } }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" + }, "node_modules/ieee754": { "version": "1.2.1", "funding": [ @@ -32345,7 +32707,6 @@ }, "node_modules/is-extendable": { "version": "1.0.1", - "devOptional": true, "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4" @@ -32356,7 +32717,6 @@ }, "node_modules/is-extendable/node_modules/is-plain-object": { "version": "2.0.4", - "devOptional": true, "license": "MIT", "dependencies": { "isobject": "^3.0.1" @@ -35630,6 +35990,11 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "dev": true, @@ -35683,6 +36048,11 @@ "node": ">=8" } }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" + }, "node_modules/kebab-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.2.tgz", @@ -35893,12 +36263,6 @@ "lie": "3.1.1" } }, - "node_modules/localforage-removeitems": { - "version": "1.4.0", - "dependencies": { - "localforage": ">=1.4.0" - } - }, "node_modules/locate-path": { "version": "6.0.0", "license": "MIT", @@ -36592,6 +36956,35 @@ "node": ">=0.10.0" } }, + "node_modules/mapbox-gl": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.15.0.tgz", + "integrity": "sha512-fjv+aYrd5TIHiL7wRa+W7KjtUqKWziJMZUkK5hm8TvJ3OLeNPx4NmW/DgfYhd/jHej8wWL+QJBDbdMMAKvNC0A==", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^2.0.1", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.4", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.4.3", + "grid-index": "^1.1.0", + "kdbush": "^4.0.1", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^2.0.0", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^8.0.0", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.3" + } + }, "node_modules/markdown-builder": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/markdown-builder/-/markdown-builder-0.9.0.tgz", @@ -39459,8 +39852,12 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "license": "MIT" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minipass": { "version": "3.3.4", @@ -39737,6 +40134,11 @@ "multicast-dns": "cli.js" } }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, "node_modules/mute-stream": { "version": "0.0.8", "dev": true, @@ -41196,6 +41598,18 @@ "through": "~2.3" } }, + "node_modules/pbf": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", + "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/pbkdf2": { "version": "3.1.2", "license": "MIT", @@ -41587,6 +42001,11 @@ "version": "4.2.0", "license": "MIT" }, + "node_modules/potpack": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", + "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" + }, "node_modules/preact": { "version": "10.11.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", @@ -41793,6 +42212,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "dev": true, @@ -42124,6 +42548,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, "node_modules/ramda": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", @@ -42208,6 +42637,14 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/react": { "version": "18.2.0", "license": "MIT", @@ -42344,6 +42781,29 @@ "version": "16.13.1", "license": "MIT" }, + "node_modules/react-map-gl": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.3.tgz", + "integrity": "sha512-uMiwk3x/XxYqSxWWBPgqitvLh8y+O9AabEIBeg3tLjoCVBEyFiZko7tAcRqA7WLT079KX/lyDL1N2zALqJb/MQ==", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1", + "@types/mapbox-gl": ">=1.0.0" + }, + "peerDependencies": { + "mapbox-gl": ">=1.13.0", + "maplibre-gl": ">=1.13.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + }, + "maplibre-gl": { + "optional": true + } + } + }, "node_modules/react-native": { "version": "0.72.3", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.72.3.tgz", @@ -42615,6 +43075,15 @@ "react-native-web": "^0.18.1" } }, + "node_modules/react-native-linear-gradient": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.1.tgz", + "integrity": "sha512-934R4Bnjo7mYT38W9ypS1Dq/YW6TgyGdkHg+w72HNxN0ZDKG1GqAnZ6XlicMUYJDh7ViiJAKN8eOF3Ho0N4J0Q==", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-localize": { "version": "2.2.6", "license": "MIT", @@ -42646,9 +43115,9 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.59", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.59.tgz", - "integrity": "sha512-eDFRT3lGol651gjGmS7HMZVPJf/wrnLa3lQUWOeK5oD0D93I+e71brrHuUu0WoSiQVT6icp+0Wx3kEdds3+spw==", + "version": "1.0.63", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.63.tgz", + "integrity": "sha512-GJc4vlhx/+vnM+xRZqT7aq/BEYMAFcPxFF5TW5OKS7j5Ba/SKMmooZB5zAutsbVq5tfh+Cfh3L2O4rNRXNjKEg==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -42659,17 +43128,13 @@ "npm": "8.11.0" }, "peerDependencies": { - "localforage": "^1.10.0", - "localforage-removeitems": "^1.4.0", + "idb-keyval": "^6.2.1", "react": ">=18.1.0", "react-native-performance": "^4.0.0", "react-native-quick-sqlite": "^8.0.0-beta.2" }, "peerDependenciesMeta": { - "localforage": { - "optional": true - }, - "localforage-removeitems": { + "idb-keyval": { "optional": true }, "react-native-performance": { @@ -42894,6 +43359,17 @@ "react-native-pager-view": "*" } }, + "node_modules/react-native-url-polyfill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz", + "integrity": "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==", + "dependencies": { + "whatwg-url-without-unicode": "8.0.0-3" + }, + "peerDependencies": { + "react-native": "*" + } + }, "node_modules/react-native-view-shot": { "version": "3.6.0", "license": "MIT", @@ -42929,6 +43405,14 @@ "react-dom": "^17.0.2 || ^18.0.0" } }, + "node_modules/react-native-web-linear-gradient": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/react-native-web-linear-gradient/-/react-native-web-linear-gradient-1.1.2.tgz", + "integrity": "sha512-SmUnpwT49CEe78pXvIvYf72Es8Pv+ZYKCnEOgb2zAKpEUDMo0+xElfRJhwt5nfI8krJ5WbFPKnoDgD0uUjAN1A==", + "peerDependencies": { + "react-native-web": "*" + } + }, "node_modules/react-native-web-lottie": { "version": "1.4.4", "license": "MIT", @@ -42958,6 +43442,18 @@ "node": ">=8" } }, + "node_modules/react-native-x-maps": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/react-native-x-maps/-/react-native-x-maps-1.0.10.tgz", + "integrity": "sha512-jBRl5JzP3QmGY6tj5CR9UwbREZ3tnuSa7puZozai3bRFrN68k3W6x1p6O8SGp91VvcQlaqJUPFZ+bkYiY3XRvA==", + "peerDependencies": { + "@rnmapbox/maps": "^10.0.11", + "mapbox-gl": "^2.15.0", + "react": "^18.2.0", + "react-map-gl": "^7.1.3", + "react-native": "^0.72.3" + } + }, "node_modules/react-native/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -44744,6 +45240,14 @@ "version": "2.2.0", "license": "MIT" }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/resolve-url": { "version": "0.2.1", "devOptional": true, @@ -44910,6 +45414,11 @@ "dev": true, "license": "ISC" }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "node_modules/rxjs": { "version": "6.6.7", "dev": true, @@ -45279,7 +45788,6 @@ }, "node_modules/set-value": { "version": "2.0.1", - "devOptional": true, "license": "MIT", "dependencies": { "extend-shallow": "^2.0.1", @@ -45293,7 +45801,6 @@ }, "node_modules/set-value/node_modules/extend-shallow": { "version": "2.0.1", - "devOptional": true, "license": "MIT", "dependencies": { "is-extendable": "^0.1.0" @@ -45304,7 +45811,6 @@ }, "node_modules/set-value/node_modules/is-extendable": { "version": "0.1.1", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -45312,7 +45818,6 @@ }, "node_modules/set-value/node_modules/is-plain-object": { "version": "2.0.4", - "devOptional": true, "license": "MIT", "dependencies": { "isobject": "^3.0.1" @@ -45888,6 +46393,46 @@ "node": ">= 10" } }, + "node_modules/sort-asc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "dependencies": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "license": "MIT" @@ -46031,7 +46576,6 @@ }, "node_modules/split-string": { "version": "3.1.0", - "devOptional": true, "license": "MIT", "dependencies": { "extend-shallow": "^3.0.0" @@ -46589,6 +47133,14 @@ "node": ">= 8.0" } }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/superstruct": { "version": "0.6.2", "license": "MIT", @@ -47187,6 +47739,11 @@ "version": "1.0.3", "license": "MIT" }, + "node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -47551,18 +48108,31 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, + "node_modules/typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" + }, "node_modules/ua-parser-js": { "version": "0.7.31", "funding": [ @@ -47705,7 +48275,6 @@ }, "node_modules/union-value": { "version": "1.0.1", - "devOptional": true, "license": "MIT", "dependencies": { "arr-union": "^3.1.0", @@ -47719,7 +48288,6 @@ }, "node_modules/union-value/node_modules/is-extendable": { "version": "0.1.1", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -48352,6 +48920,16 @@ "version": "1.1.2", "license": "MIT" }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "dev": true, @@ -49459,6 +50037,27 @@ "node": ">=12" } }, + "node_modules/whatwg-url-without-unicode": { + "version": "8.0.0-3", + "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", + "integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==", + "dependencies": { + "buffer": "^5.4.3", + "punycode": "^2.1.1", + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/whatwg-url-without-unicode/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "engines": { + "node": ">=8" + } + }, "node_modules/which": { "version": "2.0.2", "license": "ISC", @@ -52460,6 +53059,11 @@ "version": "1.2.1", "dev": true }, + "@invertase/react-native-apple-authentication": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@invertase/react-native-apple-authentication/-/react-native-apple-authentication-2.2.2.tgz", + "integrity": "sha512-uNZcUn9WbAQP5zSOFXI1+kEUokLwZG9imUulFdt5t22CU2ozGq6zyPm+BAVVg8D5eUUXduX/dJFhbuOpJxiEhQ==" + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -53487,6 +54091,25 @@ "tmp-promise": "^3.0.2" } }, + "@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "requires": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + } + }, + "@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==" + }, + "@mapbox/mapbox-gl-supported": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", + "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==" + }, "@mapbox/node-pre-gyp": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", @@ -53540,6 +54163,47 @@ } } }, + "@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, + "@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "requires": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==" + }, + "@maplibre/maplibre-gl-style-spec": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.0.tgz", + "integrity": "sha512-ZbhX9CTV+Z7vHwkRIasDOwTSzr76e8Q6a55RMsAibjyX6+P0ZNL1qAKNzOjjBDP3+aEfNMl7hHo5knuY6pTAUQ==", + "requires": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + } + }, "@mdx-js/mdx": { "version": "1.6.22", "dev": true, @@ -55620,6 +56284,12 @@ "@expo/config-plugins": "^4.0.3" } }, + "@react-native-google-signin/google-signin": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@react-native-google-signin/google-signin/-/google-signin-10.0.1.tgz", + "integrity": "sha512-oZoU2lfKyn0s0GqqdFsi4v2FSENrxQYQU9DD/RSkxDdkIQ49Wwo6p5LKlgXY04GwZEVdYMuvZN3G89gQW0ig2g==", + "requires": {} + }, "@react-native-picker/picker": { "version": "2.4.4", "requires": {} @@ -56318,6 +56988,20 @@ } } }, + "@rnmapbox/maps": { + "version": "10.0.11", + "resolved": "https://registry.npmjs.org/@rnmapbox/maps/-/maps-10.0.11.tgz", + "integrity": "sha512-CqaAOEV2nYjZzAwSd7RceGIVVIyDO0G/Vqdvgen20LDuejX9N9Yqw7BrMH8MgIH3FNFxtjwyXiw6aVtybpke0w==", + "requires": { + "@turf/along": "6.5.0", + "@turf/distance": "6.5.0", + "@turf/helpers": "6.5.0", + "@turf/length": "6.5.0", + "@turf/nearest-point-on-line": "6.5.0", + "@types/geojson": "^7946.0.7", + "debounce": "^1.2.0" + } + }, "@sentry/browser": { "version": "7.11.1", "requires": { @@ -63414,6 +64098,121 @@ "version": "0.2.0", "dev": true }, + "@turf/along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz", + "integrity": "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/bbox": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", + "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.5.0.tgz", + "integrity": "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz", + "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz", + "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==" + }, + "@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/length": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/length/-/length-6.5.0.tgz", + "integrity": "sha512-5pL5/pnw52fck3oRsHDcSGrj9HibvtlrZ0QNy2OcW8qBFDNgZ4jtl6U7eATVoyWPKBHszW3dWETW+iLV7UARig==", + "requires": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/line-intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.5.0.tgz", + "integrity": "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-rbush": "3.x" + } + }, + "@turf/line-segment": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.5.0.tgz", + "integrity": "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/nearest-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz", + "integrity": "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, "@types/acorn": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", @@ -63586,6 +64385,11 @@ "@types/node": "*" } }, + "@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" + }, "@types/glob": { "version": "7.2.0", "dev": true, @@ -63717,6 +64521,14 @@ "@types/long": { "version": "4.0.2" }, + "@types/mapbox-gl": { + "version": "2.7.13", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.13.tgz", + "integrity": "sha512-qNffhTdYkeFl8QG9Q1zPPJmcs8PvHgmLa1PcwP1rxvcfMsIgcFr/FnrCttG0ZnH7Kzdd7xfECSRNTWSr4jC3PQ==", + "requires": { + "@types/geojson": "*" + } + }, "@types/mdast": { "version": "3.0.10", "dev": true, @@ -65061,8 +65873,7 @@ "devOptional": true }, "arr-union": { - "version": "3.1.0", - "devOptional": true + "version": "3.1.0" }, "array-find-index": { "version": "1.0.2", @@ -65188,8 +65999,7 @@ "optional": true }, "assign-symbols": { - "version": "1.0.0", - "devOptional": true + "version": "1.0.0" }, "ast-types": { "version": "0.14.2", @@ -66361,6 +67171,23 @@ "bytes": { "version": "3.0.0" }, + "bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "requires": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "requires": { + "typewise-core": "^1.2" + } + }, "c8": { "version": "7.12.0", "dev": true, @@ -67047,6 +67874,14 @@ "requires": { "glob": "^7.1.6", "typescript": "^4.0.2" + }, + "dependencies": { + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } } }, "confusing-browser-globals": { @@ -67721,6 +68556,11 @@ "css-what": { "version": "6.1.0" }, + "csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==" + }, "cssesc": { "version": "3.0.0", "dev": true @@ -67796,6 +68636,11 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "debug": { "version": "4.3.4", "requires": { @@ -68305,6 +69150,11 @@ "stream-shift": "^1.0.0" } }, + "earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -69963,9 +70813,9 @@ } }, "expensify-common": { - "version": "git+ssh://git@github.com/Expensify/expensify-common.git#b60e464ca23e452eacffb93d471abed977b9abf0", - "integrity": "sha512-SA+1PDrST90MoWKNuqyfw7vT1c3S14JrrHCuk5l5m77k2T1Khu1lHPAw7sCUt0Yeoceq7JHL7zC4ZPhqVzDXwQ==", - "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#b60e464ca23e452eacffb93d471abed977b9abf0", + "version": "git+ssh://git@github.com/Expensify/expensify-common.git#4cc5f72b69bd77d2c8052a3c167d039e502a2796", + "integrity": "sha512-nNYAweSE5bwjKyFTi9tz+p1z+gxkytCnIa8M11vnseV60ZzJespcwB/2SbWkdaAL5wpvcgHLlFTTGbPUwIiTvw==", + "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#4cc5f72b69bd77d2c8052a3c167d039e502a2796", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", @@ -70096,7 +70946,6 @@ }, "extend-shallow": { "version": "3.0.2", - "devOptional": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -70506,11 +71355,6 @@ "version": "3.2.6", "dev": true }, - "flipper-plugin-bridgespy-client": { - "version": "0.1.9", - "dev": true, - "requires": {} - }, "flow-enums-runtime": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.5.tgz", @@ -70762,6 +71606,30 @@ "gensync": { "version": "1.0.0-beta.2" }, + "geojson-rbush": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.2.0.tgz", + "integrity": "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==", + "requires": { + "@turf/bbox": "*", + "@turf/helpers": "6.x", + "@turf/meta": "6.x", + "@types/geojson": "7946.0.8", + "rbush": "^3.0.1" + }, + "dependencies": { + "@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + } + } + }, + "geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, "get-caller-file": { "version": "2.0.5" }, @@ -70797,8 +71665,7 @@ } }, "get-value": { - "version": "2.0.6", - "devOptional": true + "version": "2.0.6" }, "getenv": { "version": "1.0.0" @@ -70809,6 +71676,11 @@ "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", "dev": true }, + "gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "glob": { "version": "7.1.6", "requires": { @@ -70912,6 +71784,11 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "grid-index": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", + "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==" + }, "gzip-size": { "version": "6.0.0", "dev": true, @@ -71705,6 +72582,11 @@ "postcss": "^7.0.14" } }, + "idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" + }, "ieee754": { "version": "1.2.1" }, @@ -72020,14 +72902,12 @@ }, "is-extendable": { "version": "1.0.1", - "devOptional": true, "requires": { "is-plain-object": "^2.0.4" }, "dependencies": { "is-plain-object": { "version": "2.0.4", - "devOptional": true, "requires": { "isobject": "^3.0.1" } @@ -74177,6 +75057,11 @@ "version": "1.0.1", "dev": true }, + "json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==" + }, "json-stringify-safe": { "version": "5.0.1", "dev": true @@ -74208,6 +75093,11 @@ "version": "3.1.0", "dev": true }, + "kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" + }, "kebab-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.2.tgz", @@ -74353,12 +75243,6 @@ "lie": "3.1.1" } }, - "localforage-removeitems": { - "version": "1.4.0", - "requires": { - "localforage": ">=1.4.0" - } - }, "locate-path": { "version": "6.0.0", "requires": { @@ -74845,6 +75729,35 @@ "object-visit": "^1.0.0" } }, + "mapbox-gl": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.15.0.tgz", + "integrity": "sha512-fjv+aYrd5TIHiL7wRa+W7KjtUqKWziJMZUkK5hm8TvJ3OLeNPx4NmW/DgfYhd/jHej8wWL+QJBDbdMMAKvNC0A==", + "requires": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^2.0.1", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.4", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.4.3", + "grid-index": "^1.1.0", + "kdbush": "^4.0.1", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^2.0.0", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^8.0.0", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.3" + } + }, "markdown-builder": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/markdown-builder/-/markdown-builder-0.9.0.tgz", @@ -76847,7 +77760,9 @@ } }, "minimist": { - "version": "1.2.6" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { "version": "3.3.4", @@ -77041,6 +77956,11 @@ "thunky": "^1.0.2" } }, + "murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, "mute-stream": { "version": "0.0.8", "dev": true @@ -78022,6 +78942,15 @@ "through": "~2.3" } }, + "pbf": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", + "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "requires": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + } + }, "pbkdf2": { "version": "3.1.2", "requires": { @@ -78284,6 +79213,11 @@ "postcss-value-parser": { "version": "4.2.0" }, + "potpack": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", + "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" + }, "preact": { "version": "10.11.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", @@ -78415,6 +79349,11 @@ "xtend": "^4.0.0" } }, + "protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" + }, "proxy-addr": { "version": "2.0.7", "dev": true, @@ -78632,6 +79571,11 @@ "version": "5.1.1", "dev": true }, + "quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, "ramda": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", @@ -78685,6 +79629,14 @@ "schema-utils": "^3.0.0" } }, + "rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "requires": { + "quickselect": "^2.0.0" + } + }, "react": { "version": "18.2.0", "requires": { @@ -78771,6 +79723,15 @@ "react-is": { "version": "16.13.1" }, + "react-map-gl": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.3.tgz", + "integrity": "sha512-uMiwk3x/XxYqSxWWBPgqitvLh8y+O9AabEIBeg3tLjoCVBEyFiZko7tAcRqA7WLT079KX/lyDL1N2zALqJb/MQ==", + "requires": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1", + "@types/mapbox-gl": ">=1.0.0" + } + }, "react-native": { "version": "0.72.3", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.72.3.tgz", @@ -79108,6 +80069,12 @@ "underscore": "^1.13.4" } }, + "react-native-linear-gradient": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.1.tgz", + "integrity": "sha512-934R4Bnjo7mYT38W9ypS1Dq/YW6TgyGdkHg+w72HNxN0ZDKG1GqAnZ6XlicMUYJDh7ViiJAKN8eOF3Ho0N4J0Q==", + "requires": {} + }, "react-native-localize": { "version": "2.2.6", "requires": {} @@ -79120,9 +80087,9 @@ } }, "react-native-onyx": { - "version": "1.0.59", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.59.tgz", - "integrity": "sha512-eDFRT3lGol651gjGmS7HMZVPJf/wrnLa3lQUWOeK5oD0D93I+e71brrHuUu0WoSiQVT6icp+0Wx3kEdds3+spw==", + "version": "1.0.63", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.63.tgz", + "integrity": "sha512-GJc4vlhx/+vnM+xRZqT7aq/BEYMAFcPxFF5TW5OKS7j5Ba/SKMmooZB5zAutsbVq5tfh+Cfh3L2O4rNRXNjKEg==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -79263,6 +80230,14 @@ "use-latest-callback": "^0.1.5" } }, + "react-native-url-polyfill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz", + "integrity": "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==", + "requires": { + "whatwg-url-without-unicode": "8.0.0-3" + } + }, "react-native-view-shot": { "version": "3.6.0", "requires": {} @@ -79286,6 +80261,12 @@ "styleq": "^0.1.2" } }, + "react-native-web-linear-gradient": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/react-native-web-linear-gradient/-/react-native-web-linear-gradient-1.1.2.tgz", + "integrity": "sha512-SmUnpwT49CEe78pXvIvYf72Es8Pv+ZYKCnEOgb2zAKpEUDMo0+xElfRJhwt5nfI8krJ5WbFPKnoDgD0uUjAN1A==", + "requires": {} + }, "react-native-web-lottie": { "version": "1.4.4", "requires": { @@ -79304,6 +80285,12 @@ } } }, + "react-native-x-maps": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/react-native-x-maps/-/react-native-x-maps-1.0.10.tgz", + "integrity": "sha512-jBRl5JzP3QmGY6tj5CR9UwbREZ3tnuSa7puZozai3bRFrN68k3W6x1p6O8SGp91VvcQlaqJUPFZ+bkYiY3XRvA==", + "requires": {} + }, "react-pdf": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-6.2.2.tgz", @@ -80383,6 +81370,14 @@ "resolve-pathname": { "version": "2.2.0" }, + "resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "requires": { + "protocol-buffers-schema": "^3.3.1" + } + }, "resolve-url": { "version": "0.2.1", "devOptional": true @@ -80483,6 +81478,11 @@ } } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "rxjs": { "version": "6.6.7", "dev": true, @@ -80757,7 +81757,6 @@ }, "set-value": { "version": "2.0.1", - "devOptional": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -80767,18 +81766,15 @@ "dependencies": { "extend-shallow": { "version": "2.0.1", - "devOptional": true, "requires": { "is-extendable": "^0.1.0" } }, "is-extendable": { - "version": "0.1.1", - "devOptional": true + "version": "0.1.1" }, "is-plain-object": { "version": "2.0.4", - "devOptional": true, "requires": { "isobject": "^3.0.1" } @@ -81189,6 +82185,36 @@ "socks": "^2.6.2" } }, + "sort-asc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==" + }, + "sort-desc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==" + }, + "sort-object": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "requires": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" + } + } + }, "source-list-map": { "version": "2.0.1" }, @@ -81296,7 +82322,6 @@ }, "split-string": { "version": "3.1.0", - "devOptional": true, "requires": { "extend-shallow": "^3.0.0" } @@ -81675,6 +82700,14 @@ "debug": "^4.1.0" } }, + "supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "requires": { + "kdbush": "^4.0.2" + } + }, "superstruct": { "version": "0.6.2", "requires": { @@ -82073,6 +83106,11 @@ "tiny-warning": { "version": "1.0.3" }, + "tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -82312,11 +83350,24 @@ "dev": true }, "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true }, + "typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "requires": { + "typewise-core": "^1.2.0" + } + }, + "typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" + }, "ua-parser-js": { "version": "0.7.31" }, @@ -82403,7 +83454,6 @@ }, "union-value": { "version": "1.0.1", - "devOptional": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -82412,8 +83462,7 @@ }, "dependencies": { "is-extendable": { - "version": "0.1.1", - "devOptional": true + "version": "0.1.1" } } }, @@ -82832,6 +83881,16 @@ "vm-browserify": { "version": "1.1.2" }, + "vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "requires": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "w3c-hr-time": { "version": "1.0.2", "dev": true, @@ -83552,6 +84611,23 @@ "webidl-conversions": "^7.0.0" } }, + "whatwg-url-without-unicode": { + "version": "8.0.0-3", + "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", + "integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==", + "requires": { + "buffer": "^5.4.3", + "punycode": "^2.1.1", + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" + } + } + }, "which": { "version": "2.0.2", "requires": { diff --git a/package.json b/package.json index 4f45b9d18310..17dd83bffe35 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,20 @@ { "name": "new.expensify", - "version": "1.3.53-1", + "version": "1.3.57-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.", "license": "MIT", "private": true, "scripts": { + "configure-mapbox": "scripts/setup-mapbox-sdk-walkthrough.sh", "postinstall": "scripts/postInstall.sh", "clean": "npx react-native clean-project-auto", - "android": "scripts/set-pusher-suffix.sh && npx react-native run-android", - "ios": "scripts/set-pusher-suffix.sh && npx react-native run-ios", + "android": "scripts/set-pusher-suffix.sh && npx react-native run-android --variant=developmentDebug --appId=com.expensify.chat.dev", + "ios": "scripts/set-pusher-suffix.sh && npx react-native run-ios --configuration=\"Debug Development\" --scheme=\"New Expensify Dev\"", "pod-install": "cd ios && bundle exec pod install", - "ipad": "concurrently \"npx react-native run-ios --simulator=\"iPad Pro (12.9-inch) (4th generation)\"\"", - "ipad-sm": "concurrently \"npx react-native run-ios --simulator=\"iPad Pro (9.7-inch)\"\"", + "ipad": "concurrently \"npx react-native run-ios --simulator=\\\"iPad Pro (12.9-inch) (6th generation)\\\" --configuration=\\\"Debug Development\\\" --scheme=\\\"New Expensify Dev\\\"\"", + "ipad-sm": "concurrently \"npx react-native run-ios --simulator=\\\"iPad Pro (11-inch) (4th generation)\\\" --configuration=\\\"Debug Development\\\" --scheme=\\\"New Expensify Dev\\\"\"", "start": "npx react-native start", "web": "scripts/set-pusher-suffix.sh && concurrently npm:web-proxy npm:web-server", "web-proxy": "node web/proxy.js", @@ -56,6 +57,7 @@ "@formatjs/intl-numberformat": "^8.5.0", "@formatjs/intl-pluralrules": "^5.2.2", "@gorhom/portal": "^1.0.14", + "@invertase/react-native-apple-authentication": "^2.2.2", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "7.4.0", "@react-native-async-storage/async-storage": "^1.17.10", @@ -67,11 +69,13 @@ "@react-native-firebase/app": "^12.3.0", "@react-native-firebase/crashlytics": "^12.3.0", "@react-native-firebase/perf": "^12.3.0", + "@react-native-google-signin/google-signin": "^10.0.1", "@react-native-picker/picker": "^2.4.3", "@react-navigation/material-top-tabs": "^6.6.3", "@react-navigation/native": "6.1.6", "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", + "@rnmapbox/maps": "^10.0.11", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", "babel-plugin-transform-remove-console": "^6.9.4", @@ -81,14 +85,14 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#b60e464ca23e452eacffb93d471abed977b9abf0", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#4cc5f72b69bd77d2c8052a3c167d039e502a2796", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", + "idb-keyval": "^6.2.1", "jest-when": "^3.5.2", - "localforage": "^1.10.0", - "localforage-removeitems": "^1.4.0", "lodash": "4.17.21", "lottie-react-native": "^5.1.6", + "mapbox-gl": "^2.15.0", "metro-config": "^0.71.3", "moment": "^2.29.4", "moment-timezone": "^0.5.31", @@ -101,6 +105,7 @@ "react-collapse": "^5.1.0", "react-content-loader": "^6.1.0", "react-dom": "18.1.0", + "react-map-gl": "^7.1.3", "react-native": "0.72.3", "react-native-blob-util": "^0.17.3", "react-native-collapsible": "^1.6.0", @@ -117,9 +122,10 @@ "react-native-image-picker": "^5.1.0", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#8393b7e58df6ff65fd41f60aee8ece8822c91e2b", "react-native-key-command": "^1.0.1", + "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.59", + "react-native-onyx": "1.0.63", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.6.2", "react-native-performance": "^4.0.0", @@ -134,16 +140,19 @@ "react-native-screens": "3.21.0", "react-native-svg": "^13.9.0", "react-native-tab-view": "^3.5.2", + "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", "react-native-vision-camera": "^2.15.4", + "react-native-web-linear-gradient": "^1.1.2", "react-native-web-lottie": "^1.4.4", "react-native-webview": "^11.17.2", + "react-native-x-maps": "1.0.10", "react-pdf": "^6.2.2", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", "react-window": "^1.8.9", "save": "^2.4.0", - "semver": "^7.3.8", + "semver": "^7.5.2", "shim-keyboard-event-key": "^1.0.3", "underscore": "^1.13.1" }, @@ -182,6 +191,7 @@ "@types/jest-when": "^3.5.2", "@types/js-yaml": "^4.0.5", "@types/lodash": "^4.14.195", + "@types/mapbox-gl": "^2.7.13", "@types/mock-fs": "^4.13.1", "@types/pusher-js": "^5.1.0", "@types/react": "^18.2.12", @@ -223,7 +233,6 @@ "eslint-plugin-react-native-a11y": "^3.3.0", "eslint-plugin-storybook": "^0.5.13", "eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0", - "flipper-plugin-bridgespy-client": "^0.1.9", "html-webpack-plugin": "^5.5.0", "jest": "29.4.1", "jest-circus": "29.4.1", @@ -246,7 +255,7 @@ "style-loader": "^2.0.0", "time-analytics-webpack-plugin": "^0.1.17", "type-fest": "^3.12.0", - "typescript": "^4.8.4", + "typescript": "^5.1.6", "wait-port": "^0.2.9", "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.5.0", diff --git a/patches/@react-navigation+native+6.1.6.patch b/patches/@react-navigation+native+6.1.6.patch new file mode 100644 index 000000000000..61e5eb9892e1 --- /dev/null +++ b/patches/@react-navigation+native+6.1.6.patch @@ -0,0 +1,269 @@ +diff --git a/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js b/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js +index 16fdbef..bc2c96a 100644 +--- a/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js ++++ b/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js +@@ -1,8 +1,23 @@ + import { nanoid } from 'nanoid/non-secure'; ++import { findFocusedRouteKey } from './findFocusedRouteKey'; + export default function createMemoryHistory() { + let index = 0; + let items = []; +- ++ const log = () => { ++ console.log(JSON.stringify({ ++ index, ++ indexGetter: history.index, ++ items: items.map((item, i) => { ++ var _item$state; ++ return { ++ selected: history.index === i ? '<<<<<<<' : undefined, ++ path: item.path, ++ id: item.id, ++ state: ((_item$state = item.state) === null || _item$state === void 0 ? void 0 : _item$state.key) || null ++ }; ++ }) ++ }, null, 4)); ++ }; + // Pending callbacks for `history.go(n)` + // We might modify the callback stored if it was interrupted, so we have a ref to identify it + const pending = []; +@@ -16,6 +31,9 @@ export default function createMemoryHistory() { + }); + }; + const history = { ++ get items() { ++ return items; ++ }, + get index() { + var _window$history$state; + // We store an id in the state instead of an index +@@ -32,12 +50,13 @@ export default function createMemoryHistory() { + }, + backIndex(_ref) { + let { +- path ++ path, ++ state + } = _ref; + // We need to find the index from the element before current to get closest path to go back to + for (let i = index - 1; i >= 0; i--) { + const item = items[i]; +- if (item.path === path) { ++ if (item.path === path && findFocusedRouteKey(item.state) === findFocusedRouteKey(state)) { + return i; + } + } +@@ -68,7 +87,9 @@ export default function createMemoryHistory() { + window.history.pushState({ + id + }, '', path); ++ // log(); + }, ++ + replace(_ref3) { + var _window$history$state2; + let { +@@ -108,7 +129,9 @@ export default function createMemoryHistory() { + window.history.replaceState({ + id + }, '', pathWithHash); ++ // log(); + }, ++ + // `history.go(n)` is asynchronous, there are couple of things to keep in mind: + // - it won't do anything if we can't go `n` steps, the `popstate` event won't fire. + // - each `history.go(n)` call will trigger a separate `popstate` event with correct location. +@@ -175,20 +198,17 @@ export default function createMemoryHistory() { + // But on Firefox, it seems to take much longer, around 50ms from our testing + // We're using a hacky timeout since there doesn't seem to be way to know for sure + const timer = setTimeout(() => { +- const index = pending.findIndex(it => it.ref === done); +- if (index > -1) { +- pending[index].cb(); +- pending.splice(index, 1); ++ const foundIndex = pending.findIndex(it => it.ref === done); ++ if (foundIndex > -1) { ++ pending[foundIndex].cb(); ++ pending.splice(foundIndex, 1); + } ++ index = this.index; + }, 100); + const onPopState = () => { +- var _window$history$state3; +- const id = (_window$history$state3 = window.history.state) === null || _window$history$state3 === void 0 ? void 0 : _window$history$state3.id; +- const currentIndex = items.findIndex(item => item.id === id); +- + // Fix createMemoryHistory.index variable's value + // as it may go out of sync when navigating in the browser. +- index = Math.max(currentIndex, 0); ++ index = this.index; + const last = pending.pop(); + window.removeEventListener('popstate', onPopState); + last === null || last === void 0 ? void 0 : last.cb(); +@@ -202,12 +222,17 @@ export default function createMemoryHistory() { + // Here we normalize it so that only external changes (e.g. user pressing back/forward) trigger the listener + listen(listener) { + const onPopState = () => { ++ // Fix createMemoryHistory.index variable's value ++ // as it may go out of sync when navigating in the browser. ++ index = this.index; + if (pending.length) { + // This was triggered by `history.go(n)`, we shouldn't call the listener + return; + } + listener(); ++ // log(); + }; ++ + window.addEventListener('popstate', onPopState); + return () => window.removeEventListener('popstate', onPopState); + } +diff --git a/node_modules/@react-navigation/native/lib/module/findFocusedRouteKey.js b/node_modules/@react-navigation/native/lib/module/findFocusedRouteKey.js +new file mode 100644 +index 0000000..16da117 +--- /dev/null ++++ b/node_modules/@react-navigation/native/lib/module/findFocusedRouteKey.js +@@ -0,0 +1,7 @@ ++import { findFocusedRoute } from '@react-navigation/core'; ++export const findFocusedRouteKey = state => { ++ var _findFocusedRoute; ++ // @ts-ignore ++ return (_findFocusedRoute = findFocusedRoute(state)) === null || _findFocusedRoute === void 0 ? void 0 : _findFocusedRoute.key; ++}; ++//# sourceMappingURL=findFocusedRouteKey.js.map +\ No newline at end of file +diff --git a/node_modules/@react-navigation/native/lib/module/useLinking.js b/node_modules/@react-navigation/native/lib/module/useLinking.js +index 5bf2a88..a6d0670 100644 +--- a/node_modules/@react-navigation/native/lib/module/useLinking.js ++++ b/node_modules/@react-navigation/native/lib/module/useLinking.js +@@ -2,6 +2,7 @@ import { findFocusedRoute, getActionFromState as getActionFromStateDefault, getP + import isEqual from 'fast-deep-equal'; + import * as React from 'react'; + import createMemoryHistory from './createMemoryHistory'; ++import { findFocusedRouteKey } from './findFocusedRouteKey'; + import ServerContext from './ServerContext'; + /** + * Find the matching navigation state that changed between 2 navigation states +@@ -60,6 +61,44 @@ const series = cb => { + return callback; + }; + let linkingHandlers = []; ++const getAllStateKeys = state => { ++ let current = state; ++ const keys = []; ++ if (current.routes) { ++ for (let route of current.routes) { ++ keys.push(route.key); ++ if (route.state) { ++ // @ts-ignore ++ keys.push(...getAllStateKeys(route.state)); ++ } ++ } ++ } ++ return keys; ++}; ++const getStaleHistoryDiff = (items, newState) => { ++ const newStateKeys = getAllStateKeys(newState); ++ for (let i = items.length - 1; i >= 0; i--) { ++ const itemFocusedKey = findFocusedRouteKey(items[i].state); ++ if (newStateKeys.includes(itemFocusedKey)) { ++ return items.length - i - 1; ++ } ++ } ++ return -1; ++}; ++const getHistoryDeltaByKeys = (focusedState, previousFocusedState) => { ++ const focusedStateKeys = focusedState.routes.map(r => r.key); ++ const previousFocusedStateKeys = previousFocusedState.routes.map(r => r.key); ++ const minLength = Math.min(focusedStateKeys.length, previousFocusedStateKeys.length); ++ let matchingKeys = 0; ++ for (let i = 0; i < minLength; i++) { ++ if (focusedStateKeys[i] === previousFocusedStateKeys[i]) { ++ matchingKeys++; ++ } else { ++ break; ++ } ++ } ++ return -(previousFocusedStateKeys.length - matchingKeys); ++}; + export default function useLinking(ref, _ref) { + let { + independent, +@@ -251,6 +290,9 @@ export default function useLinking(ref, _ref) { + // Otherwise it's likely a change triggered by `popstate` + path !== pendingPath) { + const historyDelta = (focusedState.history ? focusedState.history.length : focusedState.routes.length) - (previousFocusedState.history ? previousFocusedState.history.length : previousFocusedState.routes.length); ++ ++ // The historyDelta and historyDeltaByKeys may differ if the new state has an entry that didn't exist in previous state ++ const historyDeltaByKeys = getHistoryDeltaByKeys(focusedState, previousFocusedState); + if (historyDelta > 0) { + // If history length is increased, we should pushState + // Note that path might not actually change here, for example, drawer open should pushState +@@ -262,34 +304,55 @@ export default function useLinking(ref, _ref) { + // If history length is decreased, i.e. entries were removed, we want to go back + + const nextIndex = history.backIndex({ +- path ++ path, ++ state + }); + const currentIndex = history.index; + try { + if (nextIndex !== -1 && nextIndex < currentIndex) { + // An existing entry for this path exists and it's less than current index, go back to that + await history.go(nextIndex - currentIndex); ++ history.replace({ ++ path, ++ state ++ }); + } else { + // We couldn't find an existing entry to go back to, so we'll go back by the delta + // This won't be correct if multiple routes were pushed in one go before + // Usually this shouldn't happen and this is a fallback for that +- await history.go(historyDelta); ++ await history.go(historyDeltaByKeys); ++ if (historyDeltaByKeys + 1 === historyDelta) { ++ history.push({ ++ path, ++ state ++ }); ++ } else { ++ history.replace({ ++ path, ++ state ++ }); ++ } + } +- +- // Store the updated state as well as fix the path if incorrect +- history.replace({ +- path, +- state +- }); + } catch (e) { + // The navigation was interrupted + } + } else { + // If history length is unchanged, we want to replaceState +- history.replace({ +- path, +- state +- }); ++ // and remove any entries from history which focued route no longer exists in state ++ // That may happen if we replace a whole navigator. ++ const staleHistoryDiff = getStaleHistoryDiff(history.items.slice(0, history.index + 1), state); ++ if (staleHistoryDiff <= 0) { ++ history.replace({ ++ path, ++ state ++ }); ++ } else { ++ await history.go(-staleHistoryDiff); ++ history.push({ ++ path, ++ state ++ }); ++ } + } + } else { + // If no common navigation state was found, assume it's a replace diff --git a/patches/react-native+0.72.3+004+ModalKeyboardFlashing.patch b/patches/react-native+0.72.3+004+ModalKeyboardFlashing.patch new file mode 100644 index 000000000000..84a233894f94 --- /dev/null +++ b/patches/react-native+0.72.3+004+ModalKeyboardFlashing.patch @@ -0,0 +1,18 @@ +diff --git a/node_modules/react-native/React/Views/RCTModalHostViewManager.m b/node_modules/react-native/React/Views/RCTModalHostViewManager.m +index 4b9f9ad..b72984c 100644 +--- a/node_modules/react-native/React/Views/RCTModalHostViewManager.m ++++ b/node_modules/react-native/React/Views/RCTModalHostViewManager.m +@@ -79,6 +79,13 @@ RCT_EXPORT_MODULE() + if (self->_presentationBlock) { + self->_presentationBlock([modalHostView reactViewController], viewController, animated, completionBlock); + } else { ++ // In our App, If an input is blurred and a modal is opened, the rootView will become the firstResponder, which ++ // will cause system to retain a wrong keyboard state, and then the keyboard to flicker when the modal is closed. ++ // We first resign the rootView to avoid this problem. ++ UIWindow *window = RCTKeyWindow(); ++ if (window && window.rootViewController && [window.rootViewController.view isFirstResponder]) { ++ [window.rootViewController.view resignFirstResponder]; ++ } + [[modalHostView reactViewController] presentViewController:viewController + animated:animated + completion:completionBlock]; diff --git a/scripts/setup-mapbox-sdk-walkthrough.sh b/scripts/setup-mapbox-sdk-walkthrough.sh new file mode 100755 index 000000000000..20b79641fc42 --- /dev/null +++ b/scripts/setup-mapbox-sdk-walkthrough.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Mapbox SDK Credential Setup Script +# ================================== +# +# Purpose: +# -------- +# This script assists users in setting up the necessary credentials to utilize +# Mapbox's closed-source SDKs for iOS and Android. It provides step-by-step +# guidance for obtaining a secret token from Mapbox and subsequently invokes +# the "setup-mapbox-sdk.sh" script to configure the development environment. +# +# Background: +# ----------- +# To use the Mapbox SDKs for iOS and Android development, a secret token +# must be obtained from Mapbox's account page. This token is essential for +# authenticating downloads of the closed-source SDKs during the build process. +# +# Usage: +# ------ +# To configure Mapbox, invoke this script by running the following command from the project's root directory: +# npm run configure-mapbox + +# Use functions and varaibles from the utils script +source scripts/shellUtils.sh + +# Intro message +title "This script helps you set up the credential needed to use Mapbox's closed-sourced SDKs for iOS and Android." +echo -e "\n" + +echo -e "1. Visit: https://account.mapbox.com/access-tokens/\n" +echo -e "2. If you don't have a Mapbox account, create one.\n" +echo -e "3. Create a secret token needed to download Mapbox SDKs. If you haven't done this yet:" +echo -e " - Click the \"Create a token\" button." +echo -e " - Provide a descriptive name for the token (e.g., Token for SDK downloads)." +echo -e " - Ensure the checkbox next to \"Downloads:Read\" under \"Secret scopes\" is ticked." +echo -e " - All checkboxes under the \"Public scopes\" should be ticked by default. Leave them as they are." +echo -e " - Click the \"Create token\" button at the bottom of the page." +echo -e " - IMPORTANT: Copy the value of the newly created token. This is your only opportunity to do so." +echo -e "\nOnce you've done the above steps, please paste the token value below.\n" + +# Reading the secret token +read -r -s -p "Secret download token: " SECRET_TOKEN +echo -e "\n" + +if [[ -z "$SECRET_TOKEN" ]]; then + error "Token is empty. Please run the script again and provide a valid token." + exit 1 +fi + +success "Thank you for providing the token. Setting these credentials in relevant files..." +echo -e "\n" + +# Execute the configuration script +./scripts/setup-mapbox-sdk.sh "$SECRET_TOKEN" diff --git a/scripts/setup-mapbox-sdk.sh b/scripts/setup-mapbox-sdk.sh new file mode 100755 index 000000000000..06fd75fba299 --- /dev/null +++ b/scripts/setup-mapbox-sdk.sh @@ -0,0 +1,140 @@ +#!/bin/bash + +# Mapbox SDK Configuration Script for iOS and Android +# =================================================== +# +# Purpose: +# -------- +# This script configures the development environment to download Mapbox SDKs +# for both iOS and Android builds. We use Mapbox to display maps in the App. As Mapbox SDKs +# are closed-sourced, we need to authenticate with Mapbox during the download. +# +# Background: +# ----------- +# Engineers are required to obtain a secret token from Mapbox and store it on +# their development machine. This allows tools like CocoaPods for iOS or Gradle for Android +# to access the Mapbox SDK during the build process. +# +# The `.netrc` file for iOS Configuration: +# ---------------------------------------- +# The token for iOS is stored in the `.netrc` file located in the user's home directory. +# This file is used in Unix-like systems to store credentials for remote machine access. +# +# The `gradle.properties` file for Android Configuration: +# ------------------------------------------------------- +# The token for Android is stored in the `gradle.properties` file located in the .gradle directory +# in the user's home. This is accessed by the Android build system during the SDK download. +# +# How this script helps: +# ---------------------- +# This script streamlines the process of adding the credential to both the `.netrc` and +# `gradle.properties` files. When executed, it prompts the user for the secret token and +# then saves it to the respective files along with other necessary information.\n +# +# Usage: +# ------ +# To run this script, pass the secret Mapbox access token as a command-line argument: +# ./scriptname.sh YOUR_MAPBOX_ACCESS_TOKEN + +# Use functions and varaibles from the utils script +source scripts/shellUtils.sh + +NETRC_PATH="$HOME/.netrc" +GRADLE_PROPERTIES_PATH="$HOME/.gradle/gradle.properties" + +# This function provides a user-friendly error message when the script encounters an error. +# It informs the user about probable permission issues and suggests commands to resolve them. +handleError() { + echo -e "\n" + + error "The script failed." + echo "The most probable reason is permissions." + echo -e "Please ensure you have read/write permissions for the following:\n" + + echo -e "1. \033[1m$NETRC_PATH\033[0m" + echo -e "2. \033[1m$GRADLE_PROPERTIES_PATH\033[0m" + echo -e "\nYou can grant permissions using the commands:" + echo -e "\033[1mchmod u+rw $NETRC_PATH\033[0m" + echo -e "\033[1mchmod u+rw $GRADLE_PROPERTIES_PATH\033[0m" + + echo -e "\n" + exit 1 +} + +# Set a trap to call the handleError function when any of the commands fail +trap handleError ERR + +# Take the token as a command-line argument +TOKEN="$1" + +# Check if the token was provided +if [ -z "$TOKEN" ]; then + echo "Usage: $0 " + echo "No token provided. Exiting." + exit 1 +fi + +# ----------------------------------------------- +# iOS Configuration for .netrc +# ----------------------------------------------- +info "Configuring $NETRC_PATH for Mapbox iOS SDK download" + +# Check for existing Mapbox entries in .netrc +if grep -q "api.mapbox.com" "$NETRC_PATH"; then + # Extract the current token from .netrc + CURRENT_TOKEN=$(grep -A2 "api.mapbox.com" "$NETRC_PATH" | grep "password" | awk '{print $2}') + + # Compare the current token to the entered token + if [ "$CURRENT_TOKEN" == "$TOKEN" ]; then + echo -e "\nThe entered token matches the existing token in $NETRC_PATH. No changes made." + else + # Use sed to replace the old token with the new one + sed -i.bak "/api.mapbox.com/,+2s/password $CURRENT_TOKEN/password $TOKEN/" "$NETRC_PATH" + echo -e "\nToken updated in $NETRC_PATH" + fi +else + # If no existing entry, append the new credentials + { + echo "machine api.mapbox.com" + echo "login mapbox" + echo "password $TOKEN" + } >> "$NETRC_PATH" + + # Set the permissions of the .netrc file to ensure it's kept private + chmod 600 "$NETRC_PATH" + + echo -e "\n$NETRC_PATH has been updated with new credentials" +fi + +# ----------------------------------------------- +# Android Configuration for gradle.properties +# ----------------------------------------------- +echo -e "\n" +info "Configuring $GRADLE_PROPERTIES_PATH for Mapbox Android SDK download" + +# Ensure the .gradle directory exists +if [ ! -d "$HOME/.gradle" ]; then + mkdir "$HOME/.gradle" +fi + +# Check if gradle.properties exists. If not, create one. +if [ ! -f "$GRADLE_PROPERTIES_PATH" ]; then + touch "$GRADLE_PROPERTIES_PATH" +fi + +# Check if MAPBOX_DOWNLOADS_TOKEN already exists in the file +if grep -q "MAPBOX_DOWNLOADS_TOKEN" "$GRADLE_PROPERTIES_PATH"; then + # Extract the current token from gradle.properties + CURRENT_ANDROID_TOKEN=$(grep "MAPBOX_DOWNLOADS_TOKEN" "$GRADLE_PROPERTIES_PATH" | cut -d'=' -f2) + + # Compare the current token to the entered token + if [ "$CURRENT_ANDROID_TOKEN" == "$TOKEN" ]; then + echo -e "\nThe entered token matches the existing token in $GRADLE_PROPERTIES_PATH. No changes made." + else + sed -i.bak "s/MAPBOX_DOWNLOADS_TOKEN=$CURRENT_ANDROID_TOKEN/MAPBOX_DOWNLOADS_TOKEN=$TOKEN/" "$GRADLE_PROPERTIES_PATH" + echo -e "\nToken updated in $GRADLE_PROPERTIES_PATH" + fi +else + echo "MAPBOX_DOWNLOADS_TOKEN=$TOKEN" >> "$GRADLE_PROPERTIES_PATH" + echo -e "\n$GRADLE_PROPERTIES_PATH has been updated with new credentials" +fi diff --git a/src/App.js b/src/App.js index d8faa911f86b..c432a0b666c8 100644 --- a/src/App.js +++ b/src/App.js @@ -23,6 +23,7 @@ import ThemeStylesProvider from './styles/ThemeStylesProvider'; import {CurrentReportIDContextProvider} from './components/withCurrentReportID'; import {EnvironmentProvider} from './components/withEnvironment'; import * as Session from './libs/actions/Session'; +import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; // For easier debugging and development, when we are in web we expose Onyx to the window, so you can more easily set data into Onyx if (window && Environment.isDevelopment()) { @@ -40,6 +41,7 @@ LogBox.ignoreLogs([ const fill = {flex: 1}; function App() { + useDefaultDragAndDrop(); return ( { if (level === 'alert') { @@ -163,10 +167,16 @@ function Expensify(props) { appStateChangeListener.current = AppState.addEventListener('change', initializeClient); // If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report - Linking.getInitialURL().then((url) => Report.openReportFromDeepLink(url, isAuthenticated)); + Linking.getInitialURL().then((url) => { + DemoActions.runDemoByURL(url); + Report.openReportFromDeepLink(url, isAuthenticated); + }); // Open chat report from a deep link (only mobile native) - Linking.addEventListener('url', (state) => Report.openReportFromDeepLink(state.url, isAuthenticated)); + Linking.addEventListener('url', (state) => { + DemoActions.runDemoByURL(state.url); + Report.openReportFromDeepLink(state.url, isAuthenticated); + }); return () => { if (!appStateChangeListener.current) { @@ -183,12 +193,13 @@ function Expensify(props) { } return ( - + {shouldInit && ( <> + {/* We include the modal for showing a new update at the top level so the option is always present. */} {props.updateAvailable ? : null} @@ -206,6 +217,7 @@ function Expensify(props) { )} + {hasAttemptedToOpenPublicRoom && ( ; +type OnyxKey = DeepValueOf>; + +type OnyxValues = { + [ONYXKEYS.ACCOUNT]: OnyxTypes.Account; + [ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID]: string; + [ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER]: boolean; + [ONYXKEYS.ACTIVE_CLIENTS]: string[]; + [ONYXKEYS.DEVICE_ID]: string; + [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; + [ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER]: boolean; + [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.Request[]; + [ONYXKEYS.QUEUED_ONYX_UPDATES]: OnyxTypes.QueuedOnyxUpdates; + [ONYXKEYS.CURRENT_DATE]: string; + [ONYXKEYS.CREDENTIALS]: OnyxTypes.Credentials; + [ONYXKEYS.IOU]: OnyxTypes.IOU; + [ONYXKEYS.MODAL]: OnyxTypes.Modal; + [ONYXKEYS.NETWORK]: OnyxTypes.Network; + [ONYXKEYS.CUSTOM_STATUS_DRAFT]: OnyxTypes.CustomStatusDraft; + [ONYXKEYS.PERSONAL_DETAILS_LIST]: Record; + [ONYXKEYS.PRIVATE_PERSONAL_DETAILS]: OnyxTypes.PrivatePersonalDetails; + [ONYXKEYS.TASK]: OnyxTypes.Task; + [ONYXKEYS.CURRENCY_LIST]: Record; + [ONYXKEYS.UPDATE_AVAILABLE]: boolean; + [ONYXKEYS.SCREEN_SHARE_REQUEST]: OnyxTypes.ScreenShareRequest; + [ONYXKEYS.COUNTRY_CODE]: number; + [ONYXKEYS.COUNTRY]: string; + [ONYXKEYS.USER]: OnyxTypes.User; + [ONYXKEYS.LOGIN_LIST]: OnyxTypes.Login; + [ONYXKEYS.SESSION]: OnyxTypes.Session; + [ONYXKEYS.BETAS]: OnyxTypes.Beta[]; + [ONYXKEYS.PAYPAL]: OnyxTypes.Paypal; + [ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf; + [ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE]: OnyxTypes.BlockedFromConcierge; + [ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID]: string; + [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: Record; + [ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean; + [ONYXKEYS.PLAID_DATA]: OnyxTypes.PlaidData; + [ONYXKEYS.IS_PLAID_DISABLED]: boolean; + [ONYXKEYS.PLAID_LINK_TOKEN]: string; + [ONYXKEYS.ONFIDO_TOKEN]: string; + [ONYXKEYS.NVP_PREFERRED_LOCALE]: ValueOf; + [ONYXKEYS.USER_WALLET]: OnyxTypes.UserWallet; + [ONYXKEYS.WALLET_ONFIDO]: OnyxTypes.WalletOnfido; + [ONYXKEYS.WALLET_ADDITIONAL_DETAILS]: OnyxTypes.WalletAdditionalDetails; + [ONYXKEYS.WALLET_TERMS]: OnyxTypes.WalletTerms; + [ONYXKEYS.BANK_ACCOUNT_LIST]: Record; + [ONYXKEYS.FUND_LIST]: Record; + [ONYXKEYS.WALLET_STATEMENT]: OnyxTypes.WalletStatement; + [ONYXKEYS.PERSONAL_BANK_ACCOUNT]: OnyxTypes.PersonalBankAccount; + [ONYXKEYS.REIMBURSEMENT_ACCOUNT]: OnyxTypes.ReimbursementAccount; + [ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT]: OnyxTypes.ReimbursementAccountDraft; + [ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE]: string | number; + [ONYXKEYS.FREQUENTLY_USED_EMOJIS]: OnyxTypes.FrequentlyUsedEmoji[]; + [ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID]: string; + [ONYXKEYS.IS_LOADING_PAYMENT_METHODS]: boolean; + [ONYXKEYS.IS_LOADING_REPORT_DATA]: boolean; + [ONYXKEYS.IS_SHORTCUTS_MODAL_OPEN]: boolean; + [ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN]: boolean; + [ONYXKEYS.WALLET_TRANSFER]: OnyxTypes.WalletTransfer; + [ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID]: string; + [ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: boolean; + [ONYXKEYS.IS_BETA]: boolean; + [ONYXKEYS.IS_CHECKING_PUBLIC_ROOM]: boolean; + [ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS]: Record; + [ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID]: string; + [ONYXKEYS.PREFERRED_THEME]: ValueOf; + [ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS]: boolean; + [ONYXKEYS.SELECTED_TAB]: string; + [ONYXKEYS.RECEIPT_MODAL]: OnyxTypes.ReceiptModal; + [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; + [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: number; + [ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number; + + // Collections + [ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download; + [ONYXKEYS.COLLECTION.POLICY]: OnyxTypes.Policy; + [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: unknown; + [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMember; + [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember; + [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; + [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; + [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportAction; + [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: string; + [ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions; + [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: string; + [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES]: number; + [ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE]: boolean; + [ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING]: boolean; + [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; + [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; + + // Forms + [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: OnyxTypes.AddDebitCardForm; + [ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.DISPLAY_NAME_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.ROOM_NAME_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.WELCOME_MESSAGE_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.LEGAL_NAME_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.DATE_OF_BIRTH_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.HOME_ADDRESS_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.NEW_ROOM_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.ROOM_SETTINGS_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.NEW_TASK_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.EDIT_TASK_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.MONEY_REQUEST_DESCRIPTION_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.MONEY_REQUEST_MERCHANT_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.MONEY_REQUEST_AMOUNT_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.MONEY_REQUEST_DATE_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.MONEY_REQUEST_DATE_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.NEW_CONTACT_METHOD_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.PAYPAL_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.WAYPOINT_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.WAYPOINT_FORM_DRAFT]: OnyxTypes.Form; + [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_AFTER_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: OnyxTypes.Form; +}; + +export default ONYXKEYS; +export type {OnyxKey, OnyxCollectionKey, OnyxValues}; diff --git a/src/ROUTES.js b/src/ROUTES.js index 6c0365e40568..bf1beaecb3c3 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -20,7 +20,10 @@ export default { BANK_ACCOUNT_NEW: 'bank-account/new', BANK_ACCOUNT_WITH_STEP_TO_OPEN: 'bank-account/:stepToOpen?', BANK_ACCOUNT_PERSONAL: 'bank-account/personal', - getBankAccountRoute: (stepToOpen = '', policyID = '') => `bank-account/${stepToOpen}?policyID=${policyID}`, + getBankAccountRoute: (stepToOpen = '', policyID = '', backTo = '') => { + const backToParam = backTo ? `&backTo=${encodeURIComponent(backTo)}` : ''; + return `bank-account/${stepToOpen}?policyID=${policyID}${backToParam}`; + }, HOME: '', SETTINGS: 'settings', SETTINGS_PROFILE: 'settings/profile', @@ -39,14 +42,14 @@ export default { SETTINGS_CLOSE: 'settings/security/closeAccount', SETTINGS_ABOUT: 'settings/about', SETTINGS_APP_DOWNLOAD_LINKS: 'settings/about/app-download-links', - SETTINGS_PAYMENTS: 'settings/payments', - SETTINGS_ADD_PAYPAL_ME: 'settings/payments/add-paypal-me', - SETTINGS_ADD_DEBIT_CARD: 'settings/payments/add-debit-card', - SETTINGS_ADD_BANK_ACCOUNT: 'settings/payments/add-bank-account', - SETTINGS_ENABLE_PAYMENTS: 'settings/payments/enable-payments', + SETTINGS_WALLET: 'settings/wallet', + SETTINGS_ADD_PAYPAL_ME: 'settings/wallet/add-paypal-me', + SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card', + SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account', + SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments', getSettingsAddLoginRoute: (type) => `settings/addlogin/${type}`, - SETTINGS_PAYMENTS_TRANSFER_BALANCE: 'settings/payments/transfer-balance', - SETTINGS_PAYMENTS_CHOOSE_TRANSFER_ACCOUNT: 'settings/payments/choose-transfer-account', + SETTINGS_WALLET_TRANSFER_BALANCE: 'settings/wallet/transfer-balance', + SETTINGS_WALLET_CHOOSE_TRANSFER_ACCOUNT: 'settings/wallet/choose-transfer-account', SETTINGS_PERSONAL_DETAILS, SETTINGS_PERSONAL_DETAILS_LEGAL_NAME: `${SETTINGS_PERSONAL_DETAILS}/legal-name`, SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH: `${SETTINGS_PERSONAL_DETAILS}/date-of-birth`, @@ -55,11 +58,7 @@ export default { SETTINGS_CONTACT_METHOD_DETAILS: `${SETTINGS_CONTACT_METHODS}/:contactMethod/details`, getEditContactMethodRoute: (contactMethod) => `${SETTINGS_CONTACT_METHODS}/${encodeURIComponent(contactMethod)}/details`, SETTINGS_NEW_CONTACT_METHOD: `${SETTINGS_CONTACT_METHODS}/new`, - SETTINGS_2FA_IS_ENABLED: 'settings/security/two-factor-auth/enabled', - SETTINGS_2FA_DISABLE: 'settings/security/two-factor-auth/disable', - SETTINGS_2FA_CODES: 'settings/security/two-factor-auth/codes', - SETTINGS_2FA_VERIFY: 'settings/security/two-factor-auth/verify', - SETTINGS_2FA_SUCCESS: 'settings/security/two-factor-auth/success', + SETTINGS_2FA: 'settings/security/two-factor-auth', SETTINGS_STATUS, SETTINGS_STATUS_SET, NEW_GROUP: 'new/group', @@ -69,6 +68,8 @@ export default { REPORT_WITH_ID: 'r/:reportID?', EDIT_REQUEST: 'r/:threadReportID/edit/:field', getEditRequestRoute: (threadReportID, field) => `r/${threadReportID}/edit/${field}`, + EDIT_CURRENCY_REQUEST: 'r/:threadReportID/edit/currency', + getEditRequestCurrencyRoute: (threadReportID, currency, backTo) => `r/${threadReportID}/edit/currency?currency=${currency}&backTo=${backTo}`, getReportRoute: (reportID) => `r/${reportID}`, REPORT_WITH_ID_DETAILS_SHARE_CODE: 'r/:reportID/details/shareCode', getReportShareCodeRoute: (reportID) => `r/${reportID}/details/shareCode`, @@ -87,11 +88,15 @@ export default { MONEY_REQUEST_AMOUNT: ':iouType/new/amount/:reportID?', MONEY_REQUEST_PARTICIPANTS: ':iouType/new/participants/:reportID?', MONEY_REQUEST_CONFIRMATION: ':iouType/new/confirmation/:reportID?', + MONEY_REQUEST_DATE: ':iouType/new/date/:reportID?', MONEY_REQUEST_CURRENCY: ':iouType/new/currency/:reportID?', MONEY_REQUEST_DESCRIPTION: ':iouType/new/description/:reportID?', + MONEY_REQUEST_CATEGORY: ':iouType/new/category/:reportID?', + MONEY_REQUEST_MERCHANT: ':iouType/new/merchant/:reportID?', MONEY_REQUEST_MANUAL_TAB: ':iouType/new/:reportID?/manual', MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', MONEY_REQUEST_DISTANCE_TAB: ':iouType/new/:reportID?/distance', + MONEY_REQUEST_WAYPOINT: ':iouType/new/waypoint/:waypointIndex', IOU_SEND_ADD_BANK_ACCOUNT: `${IOU_SEND}/add-bank-account`, IOU_SEND_ADD_DEBIT_CARD: `${IOU_SEND}/add-debit-card`, IOU_SEND_ENABLE_PAYMENTS: `${IOU_SEND}/enable-payments`, @@ -99,8 +104,13 @@ export default { getMoneyRequestAmountRoute: (iouType, reportID = '') => `${iouType}/new/amount/${reportID}`, getMoneyRequestParticipantsRoute: (iouType, reportID = '') => `${iouType}/new/participants/${reportID}`, getMoneyRequestConfirmationRoute: (iouType, reportID = '') => `${iouType}/new/confirmation/${reportID}`, + getMoneyRequestCreatedRoute: (iouType, reportID = '') => `${iouType}/new/date/${reportID}`, getMoneyRequestCurrencyRoute: (iouType, reportID = '', currency, backTo) => `${iouType}/new/currency/${reportID}?currency=${currency}&backTo=${backTo}`, getMoneyRequestDescriptionRoute: (iouType, reportID = '') => `${iouType}/new/description/${reportID}`, + getMoneyRequestCategoryRoute: (iouType, reportID = '') => `${iouType}/new/category/${reportID}`, + getMoneyRequestMerchantRoute: (iouType, reportID = '') => `${iouType}/new/merchant/${reportID}`, + getMoneyRequestDistanceTabRoute: (iouType, reportID = '') => `${iouType}/new/${reportID}/distance`, + getMoneyRequestWaypointRoute: (iouType, waypointIndex) => `${iouType}/new/waypoint/${waypointIndex}`, SPLIT_BILL_DETAILS: `r/:reportID/split/:reportActionID`, getSplitBillDetailsRoute: (reportID, reportActionID) => `r/${reportID}/split/${reportActionID}`, getNewTaskRoute: (reportID) => `${NEW_TASK}/${reportID}`, @@ -122,7 +132,10 @@ export default { DETAILS: 'details', getDetailsRoute: (login) => `details?login=${encodeURIComponent(login)}`, PROFILE: 'a/:accountID', - getProfileRoute: (accountID) => `a/${accountID}`, + getProfileRoute: (accountID, backTo = '') => { + const backToParam = backTo ? `?backTo=${encodeURIComponent(backTo)}` : ''; + return `a/${accountID}${backToParam}`; + }, REPORT_PARTICIPANTS: 'r/:reportID/participants', getReportParticipantsRoute: (reportID) => `r/${reportID}/participants`, REPORT_WITH_ID_DETAILS: 'r/:reportID/details', @@ -143,6 +156,10 @@ export default { getGetAssistanceRoute: (taskID) => `get-assistance/${taskID}`, UNLINK_LOGIN: 'u/:accountID/:validateCode', + APPLE_SIGN_IN: 'sign-in-with-apple', + GOOGLE_SIGN_IN: 'sign-in-with-google', + DESKTOP_SIGN_IN_REDIRECT: 'desktop-signin-redirect', + // This is a special validation URL that will take the user to /workspace/new after validation. This is used // when linking users from e.com in order to share a session in this app. ENABLE_PAYMENTS: 'enable-payments', @@ -173,6 +190,10 @@ export default { getWorkspaceTravelRoute: (policyID) => `workspace/${policyID}/travel`, getWorkspaceMembersRoute: (policyID) => `workspace/${policyID}/members`, + // These are some on-off routes that will be removed once they're no longer needed (see GH issues for details) + SAASTR: 'saastr', + SBE: 'sbe', + /** * @param {String} route * @returns {Object} @@ -194,4 +215,5 @@ export default { isSubReportPageRoute: pathSegments.length > 2, }; }, + SIGN_IN_MODAL: 'sign-in-modal', }; diff --git a/src/SCREENS.js b/src/SCREENS.ts similarity index 81% rename from src/SCREENS.js rename to src/SCREENS.ts index 2e42250a8631..bcb3a02cebb4 100644 --- a/src/SCREENS.js +++ b/src/SCREENS.ts @@ -13,4 +13,6 @@ export default { PREFERENCES: 'Settings_Preferences', WORKSPACES: 'Settings_Workspaces', }, -}; + SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', + SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', +} as const; diff --git a/src/TIMEZONES.js b/src/TIMEZONES.js new file mode 100644 index 000000000000..2a596b51e8b3 --- /dev/null +++ b/src/TIMEZONES.js @@ -0,0 +1,421 @@ +// All timezones were taken from: https://raw.githubusercontent.com/leon-do/Timezones/main/timezone.json +export default [ + 'Africa/Abidjan', + 'Africa/Accra', + 'Africa/Addis_Ababa', + 'Africa/Algiers', + 'Africa/Asmara', + 'Africa/Bamako', + 'Africa/Bangui', + 'Africa/Banjul', + 'Africa/Bissau', + 'Africa/Blantyre', + 'Africa/Brazzaville', + 'Africa/Bujumbura', + 'Africa/Cairo', + 'Africa/Casablanca', + 'Africa/Ceuta', + 'Africa/Conakry', + 'Africa/Dakar', + 'Africa/Dar_es_Salaam', + 'Africa/Djibouti', + 'Africa/Douala', + 'Africa/El_Aaiun', + 'Africa/Freetown', + 'Africa/Gaborone', + 'Africa/Harare', + 'Africa/Johannesburg', + 'Africa/Juba', + 'Africa/Kampala', + 'Africa/Khartoum', + 'Africa/Kigali', + 'Africa/Kinshasa', + 'Africa/Lagos', + 'Africa/Libreville', + 'Africa/Lome', + 'Africa/Luanda', + 'Africa/Lubumbashi', + 'Africa/Lusaka', + 'Africa/Malabo', + 'Africa/Maputo', + 'Africa/Maseru', + 'Africa/Mbabane', + 'Africa/Mogadishu', + 'Africa/Monrovia', + 'Africa/Nairobi', + 'Africa/Ndjamena', + 'Africa/Niamey', + 'Africa/Nouakchott', + 'Africa/Ouagadougou', + 'Africa/Porto-Novo', + 'Africa/Sao_Tome', + 'Africa/Tripoli', + 'Africa/Tunis', + 'Africa/Windhoek', + 'America/Adak', + 'America/Anchorage', + 'America/Anguilla', + 'America/Antigua', + 'America/Araguaina', + 'America/Argentina/Buenos_Aires', + 'America/Argentina/Catamarca', + 'America/Argentina/Cordoba', + 'America/Argentina/Jujuy', + 'America/Argentina/La_Rioja', + 'America/Argentina/Mendoza', + 'America/Argentina/Rio_Gallegos', + 'America/Argentina/Salta', + 'America/Argentina/San_Juan', + 'America/Argentina/San_Luis', + 'America/Argentina/Tucuman', + 'America/Argentina/Ushuaia', + 'America/Aruba', + 'America/Asuncion', + 'America/Atikokan', + 'America/Bahia', + 'America/Bahia_Banderas', + 'America/Barbados', + 'America/Belem', + 'America/Belize', + 'America/Blanc-Sablon', + 'America/Boa_Vista', + 'America/Bogota', + 'America/Boise', + 'America/Cambridge_Bay', + 'America/Campo_Grande', + 'America/Cancun', + 'America/Caracas', + 'America/Cayenne', + 'America/Cayman', + 'America/Chicago', + 'America/Chihuahua', + 'America/Ciudad_Juarez', + 'America/Costa_Rica', + 'America/Creston', + 'America/Cuiaba', + 'America/Curacao', + 'America/Danmarkshavn', + 'America/Dawson', + 'America/Dawson_Creek', + 'America/Denver', + 'America/Detroit', + 'America/Dominica', + 'America/Edmonton', + 'America/Eirunepe', + 'America/El_Salvador', + 'America/Fort_Nelson', + 'America/Fortaleza', + 'America/Glace_Bay', + 'America/Goose_Bay', + 'America/Grand_Turk', + 'America/Grenada', + 'America/Guadeloupe', + 'America/Guatemala', + 'America/Guayaquil', + 'America/Guyana', + 'America/Halifax', + 'America/Havana', + 'America/Hermosillo', + 'America/Indiana/Indianapolis', + 'America/Indiana/Knox', + 'America/Indiana/Marengo', + 'America/Indiana/Petersburg', + 'America/Indiana/Tell_City', + 'America/Indiana/Vevay', + 'America/Indiana/Vincennes', + 'America/Indiana/Winamac', + 'America/Inuvik', + 'America/Iqaluit', + 'America/Jamaica', + 'America/Juneau', + 'America/Kentucky/Louisville', + 'America/Kentucky/Monticello', + 'America/Kralendijk', + 'America/La_Paz', + 'America/Lima', + 'America/Los_Angeles', + 'America/Lower_Princes', + 'America/Maceio', + 'America/Managua', + 'America/Manaus', + 'America/Marigot', + 'America/Martinique', + 'America/Matamoros', + 'America/Mazatlan', + 'America/Menominee', + 'America/Merida', + 'America/Metlakatla', + 'America/Mexico_City', + 'America/Miquelon', + 'America/Moncton', + 'America/Monterrey', + 'America/Montevideo', + 'America/Montserrat', + 'America/Nassau', + 'America/New_York', + 'America/Nome', + 'America/Noronha', + 'America/North_Dakota/Beulah', + 'America/North_Dakota/Center', + 'America/North_Dakota/New_Salem', + 'America/Nuuk', + 'America/Ojinaga', + 'America/Panama', + 'America/Paramaribo', + 'America/Phoenix', + 'America/Port-au-Prince', + 'America/Port_of_Spain', + 'America/Porto_Velho', + 'America/Puerto_Rico', + 'America/Punta_Arenas', + 'America/Rankin_Inlet', + 'America/Recife', + 'America/Regina', + 'America/Resolute', + 'America/Rio_Branco', + 'America/Santarem', + 'America/Santiago', + 'America/Santo_Domingo', + 'America/Sao_Paulo', + 'America/Scoresbysund', + 'America/Sitka', + 'America/St_Barthelemy', + 'America/St_Johns', + 'America/St_Kitts', + 'America/St_Lucia', + 'America/St_Thomas', + 'America/St_Vincent', + 'America/Swift_Current', + 'America/Tegucigalpa', + 'America/Thule', + 'America/Tijuana', + 'America/Toronto', + 'America/Tortola', + 'America/Vancouver', + 'America/Whitehorse', + 'America/Winnipeg', + 'America/Yakutat', + 'Antarctica/Casey', + 'Antarctica/Davis', + 'Antarctica/DumontDUrville', + 'Antarctica/Macquarie', + 'Antarctica/Mawson', + 'Antarctica/McMurdo', + 'Antarctica/Palmer', + 'Antarctica/Rothera', + 'Antarctica/Syowa', + 'Antarctica/Troll', + 'Antarctica/Vostok', + 'Arctic/Longyearbyen', + 'Asia/Aden', + 'Asia/Almaty', + 'Asia/Amman', + 'Asia/Anadyr', + 'Asia/Aqtau', + 'Asia/Aqtobe', + 'Asia/Ashgabat', + 'Asia/Atyrau', + 'Asia/Baghdad', + 'Asia/Bahrain', + 'Asia/Baku', + 'Asia/Bangkok', + 'Asia/Barnaul', + 'Asia/Beirut', + 'Asia/Bishkek', + 'Asia/Brunei', + 'Asia/Chita', + 'Asia/Choibalsan', + 'Asia/Colombo', + 'Asia/Damascus', + 'Asia/Dhaka', + 'Asia/Dili', + 'Asia/Dubai', + 'Asia/Dushanbe', + 'Asia/Famagusta', + 'Asia/Gaza', + 'Asia/Hebron', + 'Asia/Ho_Chi_Minh', + 'Asia/Hong_Kong', + 'Asia/Hovd', + 'Asia/Irkutsk', + 'Asia/Jakarta', + 'Asia/Jayapura', + 'Asia/Jerusalem', + 'Asia/Kabul', + 'Asia/Kamchatka', + 'Asia/Karachi', + 'Asia/Kathmandu', + 'Asia/Khandyga', + 'Asia/Kolkata', + 'Asia/Krasnoyarsk', + 'Asia/Kuala_Lumpur', + 'Asia/Kuching', + 'Asia/Kuwait', + 'Asia/Macau', + 'Asia/Magadan', + 'Asia/Makassar', + 'Asia/Manila', + 'Asia/Muscat', + 'Asia/Nicosia', + 'Asia/Novokuznetsk', + 'Asia/Novosibirsk', + 'Asia/Omsk', + 'Asia/Oral', + 'Asia/Phnom_Penh', + 'Asia/Pontianak', + 'Asia/Pyongyang', + 'Asia/Qatar', + 'Asia/Qostanay', + 'Asia/Qyzylorda', + 'Asia/Riyadh', + 'Asia/Sakhalin', + 'Asia/Samarkand', + 'Asia/Seoul', + 'Asia/Shanghai', + 'Asia/Singapore', + 'Asia/Srednekolymsk', + 'Asia/Taipei', + 'Asia/Tashkent', + 'Asia/Tbilisi', + 'Asia/Tehran', + 'Asia/Thimphu', + 'Asia/Tokyo', + 'Asia/Tomsk', + 'Asia/Ulaanbaatar', + 'Asia/Urumqi', + 'Asia/Ust-Nera', + 'Asia/Vientiane', + 'Asia/Vladivostok', + 'Asia/Yakutsk', + 'Asia/Yangon', + 'Asia/Yekaterinburg', + 'Asia/Yerevan', + 'Atlantic/Azores', + 'Atlantic/Bermuda', + 'Atlantic/Canary', + 'Atlantic/Cape_Verde', + 'Atlantic/Faroe', + 'Atlantic/Madeira', + 'Atlantic/Reykjavik', + 'Atlantic/South_Georgia', + 'Atlantic/St_Helena', + 'Atlantic/Stanley', + 'Australia/Adelaide', + 'Australia/Brisbane', + 'Australia/Broken_Hill', + 'Australia/Darwin', + 'Australia/Eucla', + 'Australia/Hobart', + 'Australia/Lindeman', + 'Australia/Lord_Howe', + 'Australia/Melbourne', + 'Australia/Perth', + 'Australia/Sydney', + 'Europe/Amsterdam', + 'Europe/Andorra', + 'Europe/Astrakhan', + 'Europe/Athens', + 'Europe/Belgrade', + 'Europe/Berlin', + 'Europe/Bratislava', + 'Europe/Brussels', + 'Europe/Bucharest', + 'Europe/Budapest', + 'Europe/Busingen', + 'Europe/Chisinau', + 'Europe/Copenhagen', + 'Europe/Dublin', + 'Europe/Gibraltar', + 'Europe/Guernsey', + 'Europe/Helsinki', + 'Europe/Isle_of_Man', + 'Europe/Istanbul', + 'Europe/Jersey', + 'Europe/Kaliningrad', + 'Europe/Kirov', + 'Europe/Kyiv', + 'Europe/Lisbon', + 'Europe/Ljubljana', + 'Europe/London', + 'Europe/Luxembourg', + 'Europe/Madrid', + 'Europe/Malta', + 'Europe/Mariehamn', + 'Europe/Minsk', + 'Europe/Monaco', + 'Europe/Moscow', + 'Europe/Oslo', + 'Europe/Paris', + 'Europe/Podgorica', + 'Europe/Prague', + 'Europe/Riga', + 'Europe/Rome', + 'Europe/Samara', + 'Europe/San_Marino', + 'Europe/Sarajevo', + 'Europe/Saratov', + 'Europe/Simferopol', + 'Europe/Skopje', + 'Europe/Sofia', + 'Europe/Stockholm', + 'Europe/Tallinn', + 'Europe/Tirane', + 'Europe/Ulyanovsk', + 'Europe/Vaduz', + 'Europe/Vatican', + 'Europe/Vienna', + 'Europe/Vilnius', + 'Europe/Volgograd', + 'Europe/Warsaw', + 'Europe/Zagreb', + 'Europe/Zurich', + 'Indian/Antananarivo', + 'Indian/Chagos', + 'Indian/Christmas', + 'Indian/Cocos', + 'Indian/Comoro', + 'Indian/Kerguelen', + 'Indian/Mahe', + 'Indian/Maldives', + 'Indian/Mauritius', + 'Indian/Mayotte', + 'Indian/Reunion', + 'Pacific/Apia', + 'Pacific/Auckland', + 'Pacific/Bougainville', + 'Pacific/Chatham', + 'Pacific/Chuuk', + 'Pacific/Easter', + 'Pacific/Efate', + 'Pacific/Fakaofo', + 'Pacific/Fiji', + 'Pacific/Funafuti', + 'Pacific/Galapagos', + 'Pacific/Gambier', + 'Pacific/Guadalcanal', + 'Pacific/Guam', + 'Pacific/Honolulu', + 'Pacific/Kanton', + 'Pacific/Kiritimati', + 'Pacific/Kosrae', + 'Pacific/Kwajalein', + 'Pacific/Majuro', + 'Pacific/Marquesas', + 'Pacific/Midway', + 'Pacific/Nauru', + 'Pacific/Niue', + 'Pacific/Norfolk', + 'Pacific/Noumea', + 'Pacific/Pago_Pago', + 'Pacific/Palau', + 'Pacific/Pitcairn', + 'Pacific/Pohnpei', + 'Pacific/Port_Moresby', + 'Pacific/Rarotonga', + 'Pacific/Saipan', + 'Pacific/Tahiti', + 'Pacific/Tarawa', + 'Pacific/Tongatapu', + 'Pacific/Wake', + 'Pacific/Wallis', +]; diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index ff97c9be24a6..d50fad0bd2f0 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React from 'react'; +import React, {useEffect, useRef, useCallback} from 'react'; import {ActivityIndicator, View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; @@ -10,17 +10,16 @@ import * as BankAccounts from '../libs/actions/BankAccounts'; import ONYXKEYS from '../ONYXKEYS'; import styles from '../styles/styles'; import themeColors from '../styles/themes/default'; -import compose from '../libs/compose'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Picker from './Picker'; import {plaidDataPropTypes} from '../pages/ReimbursementAccount/plaidDataPropTypes'; import Text from './Text'; import getBankIcon from './Icon/BankIcons'; import Icon from './Icon'; import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlockingView'; -import {withNetwork} from './OnyxProvider'; import CONST from '../CONST'; import KeyboardShortcut from '../libs/KeyboardShortcut'; +import useLocalize from '../hooks/useLocalize'; +import useNetwork from '../hooks/useNetwork'; const propTypes = { /** Contains plaid data */ @@ -52,8 +51,6 @@ const propTypes = { /** Are we adding a withdrawal account? */ allowDebit: PropTypes.bool, - - ...withLocalizePropTypes, }; const defaultProps = { @@ -68,172 +65,163 @@ const defaultProps = { bankAccountID: 0, }; -class AddPlaidBankAccount extends React.Component { - constructor(props) { - super(props); - - this.getPlaidLinkToken = this.getPlaidLinkToken.bind(this); - this.subscribedKeyboardShortcuts = []; - } - - componentDidMount() { - this.subscribeToNavigationShortcuts(); - - // If we're coming from Plaid OAuth flow then we need to reuse the existing plaidLinkToken - if (this.isAuthenticatedWithPlaid()) { - return; - } - - BankAccounts.openPlaidBankLogin(this.props.allowDebit, this.props.bankAccountID); - } - - componentDidUpdate(prevProps) { - if (!prevProps.network.isOffline || this.props.network.isOffline || this.isAuthenticatedWithPlaid()) { - return; - } - - // If we are coming back from offline and we haven't authenticated with Plaid yet, we need to re-run our call to kick off Plaid - BankAccounts.openPlaidBankLogin(this.props.allowDebit, this.props.bankAccountID); - } +function AddPlaidBankAccount({plaidData, selectedPlaidAccountID, plaidLinkToken, onExitPlaid, onSelect, text, receivedRedirectURI, plaidLinkOAuthToken, bankAccountID, allowDebit}) { + const subscribedKeyboardShortcuts = useRef([]); + const previousNetworkState = useRef(); - componentWillUnmount() { - this.unsubscribeToNavigationShortcuts(); - } + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); /** * @returns {String} */ - getPlaidLinkToken() { - if (this.props.plaidLinkToken) { - return this.props.plaidLinkToken; + const getPlaidLinkToken = () => { + if (plaidLinkToken) { + return plaidLinkToken; } - if (this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) { - return this.props.plaidLinkOAuthToken; + if (receivedRedirectURI && plaidLinkOAuthToken) { + return plaidLinkOAuthToken; } - } + }; /** * @returns {Boolean} + * I'm using useCallback so the useEffect which uses this function doesn't run on every render. */ - isAuthenticatedWithPlaid() { - return ( - (this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) || - !_.isEmpty(lodashGet(this.props.plaidData, 'bankAccounts')) || - !_.isEmpty(lodashGet(this.props.plaidData, 'errors')) - ); - } + const isAuthenticatedWithPlaid = useCallback( + () => (receivedRedirectURI && plaidLinkOAuthToken) || !_.isEmpty(lodashGet(plaidData, 'bankAccounts')) || !_.isEmpty(lodashGet(plaidData, 'errors')), + [plaidData, plaidLinkOAuthToken, receivedRedirectURI], + ); /** * Blocks the keyboard shortcuts that can navigate */ - subscribeToNavigationShortcuts() { + const subscribeToNavigationShortcuts = () => { // find and block the shortcuts const shortcutsToBlock = _.filter(CONST.KEYBOARD_SHORTCUTS, (x) => x.type === CONST.KEYBOARD_SHORTCUTS_TYPES.NAVIGATION_SHORTCUT); - this.subscribedKeyboardShortcuts = _.map(shortcutsToBlock, (shortcut) => + subscribedKeyboardShortcuts.current = _.map(shortcutsToBlock, (shortcut) => KeyboardShortcut.subscribe( shortcut.shortcutKey, () => {}, // do nothing shortcut.descriptionKey, shortcut.modifiers, false, - () => lodashGet(this.props.plaidData, 'bankAccounts', []).length > 0, // start bubbling when there are bank accounts + () => lodashGet(plaidData, 'bankAccounts', []).length > 0, // start bubbling when there are bank accounts ), ); - } + }; /** * Unblocks the keyboard shortcuts that can navigate */ - unsubscribeToNavigationShortcuts() { - _.each(this.subscribedKeyboardShortcuts, (unsubscribe) => unsubscribe()); - this.subscribedKeyboardShortcuts = []; - } + const unsubscribeToNavigationShortcuts = () => { + _.each(subscribedKeyboardShortcuts.current, (unsubscribe) => unsubscribe()); + subscribedKeyboardShortcuts.current = []; + }; - render() { - const plaidBankAccounts = lodashGet(this.props.plaidData, 'bankAccounts') || []; - const token = this.getPlaidLinkToken(); - const options = _.map(plaidBankAccounts, (account) => ({ - value: account.plaidAccountID, - label: `${account.addressName} ${account.mask}`, - })); - const {icon, iconSize} = getBankIcon(); - const plaidErrors = lodashGet(this.props.plaidData, 'errors'); - const plaidDataErrorMessage = !_.isEmpty(plaidErrors) ? _.chain(plaidErrors).values().first().value() : ''; - const bankName = lodashGet(this.props.plaidData, 'bankName'); - - // Plaid Link view - if (!plaidBankAccounts.length) { - return ( - - {lodashGet(this.props.plaidData, 'isLoading') && ( - - - - )} - {Boolean(plaidDataErrorMessage) && {plaidDataErrorMessage}} - {Boolean(token) && !bankName && ( - { - Log.info('[PlaidLink] Success!'); - BankAccounts.openPlaidBankAccountSelector(publicToken, metadata.institution.name, this.props.allowDebit, this.props.bankAccountID); - }} - onError={(error) => { - Log.hmmm('[PlaidLink] Error: ', error.message); - }} - // User prematurely exited the Plaid flow - // eslint-disable-next-line react/jsx-props-no-multi-spaces - onExit={this.props.onExitPlaid} - receivedRedirectURI={this.props.receivedRedirectURI} - /> - )} - - ); + useEffect(() => { + subscribeToNavigationShortcuts(); + + // If we're coming from Plaid OAuth flow then we need to reuse the existing plaidLinkToken + if (isAuthenticatedWithPlaid()) { + return unsubscribeToNavigationShortcuts; } + BankAccounts.openPlaidBankLogin(allowDebit, bankAccountID); + return unsubscribeToNavigationShortcuts; - // Plaid bank accounts view + // disabling this rule, as we want this to run only on the first render + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + // If we are coming back from offline and we haven't authenticated with Plaid yet, we need to re-run our call to kick off Plaid + // previousNetworkState.current also makes sure that this doesn't run on the first render. + if (previousNetworkState.current && !isOffline && !isAuthenticatedWithPlaid()) { + BankAccounts.openPlaidBankLogin(allowDebit, bankAccountID); + } + previousNetworkState.current = isOffline; + }, [allowDebit, bankAccountID, isAuthenticatedWithPlaid, isOffline]); + + const plaidBankAccounts = lodashGet(plaidData, 'bankAccounts') || []; + const token = getPlaidLinkToken(); + const options = _.map(plaidBankAccounts, (account) => ({ + value: account.plaidAccountID, + label: `${account.addressName} ${account.mask}`, + })); + const {icon, iconSize} = getBankIcon(); + const plaidErrors = lodashGet(plaidData, 'errors'); + const plaidDataErrorMessage = !_.isEmpty(plaidErrors) ? _.chain(plaidErrors).values().first().value() : ''; + const bankName = lodashGet(plaidData, 'bankName'); + + // Plaid Link view + if (!plaidBankAccounts.length) { return ( - {!_.isEmpty(this.props.text) && {this.props.text}} - - - {bankName} - - - + + + )} + {Boolean(plaidDataErrorMessage) && {plaidDataErrorMessage}} + {Boolean(token) && !bankName && ( + { + Log.info('[PlaidLink] Success!'); + BankAccounts.openPlaidBankAccountSelector(publicToken, metadata.institution.name, allowDebit, bankAccountID); + }} + onError={(error) => { + Log.hmmm('[PlaidLink] Error: ', error.message); }} - value={this.props.selectedPlaidAccountID} + // User prematurely exited the Plaid flow + // eslint-disable-next-line react/jsx-props-no-multi-spaces + onExit={onExitPlaid} + receivedRedirectURI={receivedRedirectURI} /> - + )} ); } + + // Plaid bank accounts view + return ( + + {!_.isEmpty(text) && {text}} + + + {bankName} + + + + + + ); } AddPlaidBankAccount.propTypes = propTypes; AddPlaidBankAccount.defaultProps = defaultProps; - -export default compose( - withLocalize, - withNetwork(), - withOnyx({ - plaidLinkToken: { - key: ONYXKEYS.PLAID_LINK_TOKEN, - initWithStoredValues: false, - }, - }), -)(AddPlaidBankAccount); +AddPlaidBankAccount.displayName = 'AddPlaidBankAccount'; + +export default withOnyx({ + plaidLinkToken: { + key: ONYXKEYS.PLAID_LINK_TOKEN, + initWithStoredValues: false, + }, +})(AddPlaidBankAccount); diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index e8a41ec35435..204333474849 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -1,9 +1,10 @@ import _ from 'underscore'; import React, {useMemo, useRef, useState} from 'react'; import PropTypes from 'prop-types'; -import {LogBox, ScrollView, View} from 'react-native'; +import {LogBox, ScrollView, View, Text} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; import lodashGet from 'lodash/get'; +import compose from '../../libs/compose'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import styles from '../../styles/styles'; import themeColors from '../../styles/themes/default'; @@ -14,6 +15,8 @@ import CONST from '../../CONST'; import * as StyleUtils from '../../styles/StyleUtils'; import resetDisplayListViewBorderOnBlur from './resetDisplayListViewBorderOnBlur'; import variables from '../../styles/variables'; +import {withNetwork} from '../OnyxProvider'; +import networkPropTypes from '../networkPropTypes'; // The error that's being thrown below will be ignored until we fork the // react-native-google-places-autocomplete repo and replace the @@ -48,6 +51,9 @@ const propTypes = { /** A callback function when the value of this field has changed */ onInputChange: PropTypes.func.isRequired, + /** A callback function when an address has been auto-selected */ + onPress: PropTypes.func, + /** Customize the TextInput container */ // eslint-disable-next-line react/forbid-prop-types containerStyles: PropTypes.arrayOf(PropTypes.object), @@ -58,14 +64,20 @@ const propTypes = { /** A map of inputID key names */ renamedInputKeys: PropTypes.shape({ street: PropTypes.string, + street2: PropTypes.string, city: PropTypes.string, state: PropTypes.string, + lat: PropTypes.string, + lng: PropTypes.string, zipCode: PropTypes.string, }), /** Maximum number of characters allowed in search input */ maxInputLength: PropTypes.number, + /** Information about the network */ + network: networkPropTypes.isRequired, + ...withLocalizePropTypes, }; @@ -73,6 +85,7 @@ const defaultProps = { inputID: undefined, shouldSaveDraft: false, onBlur: () => {}, + onPress: () => {}, errorText: '', hint: '', value: undefined, @@ -81,9 +94,12 @@ const defaultProps = { isLimitedToUSA: true, renamedInputKeys: { street: 'addressStreet', + street2: 'addressStreet2', city: 'addressCity', state: 'addressState', zipCode: 'addressZipCode', + lat: 'addressLat', + lng: 'addressLng', }, maxInputLength: undefined, }; @@ -166,6 +182,9 @@ function AddressSearch(props) { zipCode, country: '', state: state || stateAutoCompleteFallback, + lat: lodashGet(details, 'geometry.location.lat', 0), + lng: lodashGet(details, 'geometry.location.lng', 0), + address: lodashGet(details, 'formatted_address', ''), }; // If the address is not in the US, use the full length state name since we're displaying the address's @@ -194,11 +213,16 @@ function AddressSearch(props) { if (props.inputID) { _.each(values, (value, key) => { const inputKey = lodashGet(props.renamedInputKeys, key, key); + if (!inputKey) { + return; + } props.onInputChange(value, inputKey); }); } else { props.onInputChange(values); } + + props.onPress(values); }; return ( @@ -226,6 +250,11 @@ function AddressSearch(props) { fetchDetails suppressDefaultStyles enablePoweredByContainer={false} + ListEmptyComponent={ + props.network.isOffline ? null : ( + {props.translate('common.noResultsFound')} + ) + } onPress={(data, details) => { saveLocationDetails(data, details); @@ -306,7 +335,10 @@ AddressSearch.propTypes = propTypes; AddressSearch.defaultProps = defaultProps; AddressSearch.displayName = 'AddressSearch'; -export default withLocalize( +export default compose( + withNetwork(), + withLocalize, +)( React.forwardRef((props, ref) => ( { + if (!linkProps.onPress) { + return; + } + + event.preventDefault(); + linkProps.onPress(); + }} onPressIn={onPressIn} onPressOut={onPressOut} accessibilityRole={CONST.ACCESSIBILITY_ROLE.LINK} @@ -80,14 +87,7 @@ function BaseAnchorForCommentsOnly({onPressIn, onPressOut, href = '', rel = '', target: isEmail || !linkProps.href ? '_self' : target, }} href={linkProps.href || href} - onPress={(event) => { - if (!linkProps.onPress) { - return; - } - - event.preventDefault(); - linkProps.onPress(); - }} + suppressHighlighting // Add testID so it gets selected as an anchor tag by SelectionScraper testID="a" // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/AnimatedStep/AnimatedStepContext.js b/src/components/AnimatedStep/AnimatedStepContext.js new file mode 100644 index 000000000000..30377147fdb8 --- /dev/null +++ b/src/components/AnimatedStep/AnimatedStepContext.js @@ -0,0 +1,5 @@ +import {createContext} from 'react'; + +const AnimatedStepContext = createContext(); + +export default AnimatedStepContext; diff --git a/src/components/AnimatedStep/AnimatedStepProvider.js b/src/components/AnimatedStep/AnimatedStepProvider.js new file mode 100644 index 000000000000..280fbd1a2776 --- /dev/null +++ b/src/components/AnimatedStep/AnimatedStepProvider.js @@ -0,0 +1,17 @@ +import React, {useState} from 'react'; +import PropTypes from 'prop-types'; +import AnimatedStepContext from './AnimatedStepContext'; +import CONST from '../../CONST'; + +const propTypes = { + children: PropTypes.node.isRequired, +}; + +function AnimatedStepProvider({children}) { + const [animationDirection, setAnimationDirection] = useState(CONST.ANIMATION_DIRECTION.IN); + + return {children}; +} + +AnimatedStepProvider.propTypes = propTypes; +export default AnimatedStepProvider; diff --git a/src/components/AnimatedStep.js b/src/components/AnimatedStep/index.js similarity index 58% rename from src/components/AnimatedStep.js rename to src/components/AnimatedStep/index.js index dce06cb33760..a8b9b80fcc0e 100644 --- a/src/components/AnimatedStep.js +++ b/src/components/AnimatedStep/index.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import * as Animatable from 'react-native-animatable'; -import CONST from '../CONST'; -import styles from '../styles/styles'; +import CONST from '../../CONST'; +import styles from '../../styles/styles'; const propTypes = { /** Children to wrap in AnimatedStep. */ @@ -14,27 +14,37 @@ const propTypes = { /** Whether we're animating the step in or out */ direction: PropTypes.oneOf(['in', 'out']), + + /** Callback to fire when the animation ends */ + onAnimationEnd: PropTypes.func, }; const defaultProps = { direction: 'in', style: [], + onAnimationEnd: () => {}, }; -function AnimatedStep(props) { - function getAnimationStyle(direction) { - let animationStyle; - - if (direction === 'in') { - animationStyle = styles.makeSlideInTranslation('translateX', CONST.ANIMATED_TRANSITION_FROM_VALUE); - } else if (direction === 'out') { - animationStyle = styles.makeSlideInTranslation('translateX', -CONST.ANIMATED_TRANSITION_FROM_VALUE); - } - return animationStyle; +function getAnimationStyle(direction) { + let transitionValue; + + if (direction === 'in') { + transitionValue = CONST.ANIMATED_TRANSITION_FROM_VALUE; + } else if (direction === 'out') { + transitionValue = -CONST.ANIMATED_TRANSITION_FROM_VALUE; } + return styles.makeSlideInTranslation('translateX', transitionValue); +} +function AnimatedStep(props) { return ( { + if (!props.onAnimationEnd) { + return; + } + props.onAnimationEnd(); + }} duration={CONST.ANIMATED_TRANSITION} animation={getAnimationStyle(props.direction)} useNativeDriver diff --git a/src/components/AnimatedStep/useAnimatedStepContext.js b/src/components/AnimatedStep/useAnimatedStepContext.js new file mode 100644 index 000000000000..e2af9514e20e --- /dev/null +++ b/src/components/AnimatedStep/useAnimatedStepContext.js @@ -0,0 +1,12 @@ +import {useContext} from 'react'; +import AnimatedStepContext from './AnimatedStepContext'; + +function useAnimatedStepContext() { + const context = useContext(AnimatedStepContext); + if (!context) { + throw new Error('useAnimatedStepContext must be used within an AnimatedStepContextProvider'); + } + return context; +} + +export default useAnimatedStepContext; diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 665123852b77..c07a4474a68b 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -25,6 +25,7 @@ import HeaderGap from './HeaderGap'; import SafeAreaConsumer from './SafeAreaConsumer'; import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL'; import reportPropTypes from '../pages/reportPropTypes'; +import tryResolveUrlFromApiRoot from '../libs/tryResolveUrlFromApiRoot'; /** * Modal render prop component that exposes modal launching triggers that can be used @@ -71,6 +72,9 @@ const propTypes = { ...withLocalizePropTypes, ...windowDimensionsPropTypes, + + /** Denotes whether it is a workspace avatar or not */ + isWorkspaceAvatar: PropTypes.bool, }; const defaultProps = { @@ -86,6 +90,7 @@ const defaultProps = { onModalShow: () => {}, onModalHide: () => {}, onCarouselAttachmentChange: () => {}, + isWorkspaceAvatar: false, }; function AttachmentModal(props) { @@ -93,12 +98,14 @@ function AttachmentModal(props) { const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false); const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); const [isAuthTokenRequired, setIsAuthTokenRequired] = useState(props.isAuthTokenRequired); + const [isAttachmentReceipt, setIsAttachmentReceipt] = useState(false); const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState(''); const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null); const [source, setSource] = useState(props.source); const [modalType, setModalType] = useState(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE); const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false); const [confirmButtonFadeAnimation] = useState(new Animated.Value(1)); + const [shouldShowDownloadButton, setShouldShowDownloadButton] = React.useState(true); const [file, setFile] = useState( props.originalFileName ? { @@ -112,12 +119,13 @@ function AttachmentModal(props) { /** * Keeps the attachment source in sync with the attachment displayed currently in the carousel. - * @param {{ source: String, isAuthTokenRequired: Boolean, file: { name: string } }} attachment + * @param {{ source: String, isAuthTokenRequired: Boolean, file: { name: string }, isReceipt: Boolean }} attachment */ const onNavigate = useCallback( (attachment) => { setSource(attachment.source); setFile(attachment.file); + setIsAttachmentReceipt(attachment.isReceipt); setIsAuthTokenRequired(attachment.isAuthTokenRequired); onCarouselAttachmentChange(attachment); }, @@ -138,6 +146,16 @@ function AttachmentModal(props) { [translate], ); + const setDownloadButtonVisibility = useCallback( + (shouldShowButton) => { + if (shouldShowDownloadButton === shouldShowButton) { + return; + } + setShouldShowDownloadButton(shouldShowButton); + }, + [shouldShowDownloadButton], + ); + /** * Download the currently viewed attachment. */ @@ -298,6 +316,7 @@ function AttachmentModal(props) { }, []); const sourceForAttachmentView = props.source || source; + return ( <> {props.isSmallScreenWidth && } downloadAttachment(source)} shouldShowCloseButton={!props.isSmallScreenWidth} shouldShowBackButton={props.isSmallScreenWidth} @@ -332,9 +351,10 @@ function AttachmentModal(props) { ) : ( Boolean(sourceForAttachmentView) && @@ -345,6 +365,7 @@ function AttachmentModal(props) { isAuthTokenRequired={isAuthTokenRequired} file={file} onToggleKeyboard={updateConfirmButtonVisibility} + isWorkspaceAvatar={props.isWorkspaceAvatar} /> ) )} diff --git a/src/components/AttachmentPicker/index.js b/src/components/AttachmentPicker/index.js index e7653df2b4d0..9ea94ae53d42 100644 --- a/src/components/AttachmentPicker/index.js +++ b/src/components/AttachmentPicker/index.js @@ -27,6 +27,8 @@ function getAcceptableFileTypes(type) { function AttachmentPicker(props) { const fileInput = useRef(); const onPicked = useRef(); + const onCanceled = useRef(() => {}); + return ( <> e.stopPropagation()} + onClick={(e) => { + e.stopPropagation(); + if (!fileInput.current) { + return; + } + fileInput.current.addEventListener('cancel', () => onCanceled.current(), {once: true}); + }} accept={getAcceptableFileTypes(props.type)} /> {props.children({ - openPicker: ({onPicked: newOnPicked}) => { + openPicker: ({onPicked: newOnPicked, onCanceled: newOnCanceled = () => {}}) => { onPicked.current = newOnPicked; fileInput.current.click(); + onCanceled.current = newOnCanceled; }, })} diff --git a/src/components/AttachmentPicker/index.native.js b/src/components/AttachmentPicker/index.native.js index b4b7d0b04c4e..8b1bb54da920 100644 --- a/src/components/AttachmentPicker/index.native.js +++ b/src/components/AttachmentPicker/index.native.js @@ -1,30 +1,24 @@ -/** - * The react native image/document pickers work for iOS/Android, but we want to wrap them both within AttachmentPicker - */ import _ from 'underscore'; -import React, {Component} from 'react'; -import {Alert, Linking, View} from 'react-native'; -import {launchImageLibrary} from 'react-native-image-picker'; +import React, {useState, useRef, useCallback, useMemo} from 'react'; +import {View, Alert, Linking} from 'react-native'; import RNDocumentPicker from 'react-native-document-picker'; import RNFetchBlob from 'react-native-blob-util'; +import {launchImageLibrary} from 'react-native-image-picker'; import {propTypes as basePropTypes, defaultProps} from './attachmentPickerPropTypes'; -import styles from '../../styles/styles'; -import Popover from '../Popover'; -import MenuItem from '../MenuItem'; -import * as Expensicons from '../Icon/Expensicons'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; -import withLocalize, {withLocalizePropTypes} from '../withLocalize'; -import compose from '../../libs/compose'; -import launchCamera from './launchCamera'; import CONST from '../../CONST'; import * as FileUtils from '../../libs/fileDownload/FileUtils'; -import ArrowKeyFocusManager from '../ArrowKeyFocusManager'; -import KeyboardShortcut from '../../libs/KeyboardShortcut'; +import * as Expensicons from '../Icon/Expensicons'; +import launchCamera from './launchCamera'; +import Popover from '../Popover'; +import MenuItem from '../MenuItem'; +import styles from '../../styles/styles'; +import useLocalize from '../../hooks/useLocalize'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; +import useKeyboardShortcut from '../../hooks/useKeyboardShortcut'; +import useArrowKeyFocusManager from '../../hooks/useArrowKeyFocusManager'; const propTypes = { ...basePropTypes, - ...windowDimensionsPropTypes, - ...withLocalizePropTypes, }; /** @@ -43,14 +37,14 @@ const imagePickerOptions = { * @param {String} type * @returns {Object} */ -function getImagePickerOptions(type) { +const getImagePickerOptions = (type) => { // mediaType property is one of the ImagePicker configuration to restrict types' const mediaType = type === CONST.ATTACHMENT_PICKER_TYPE.IMAGE ? 'photo' : 'mixed'; return { mediaType, ...imagePickerOptions, }; -} +}; /** * See https://github.com/rnmods/react-native-document-picker#options for DocumentPicker configuration options @@ -67,7 +61,7 @@ const documentPickerOptions = { * @param {Object} fileData * @return {Promise} */ -function getDataForUpload(fileData) { +const getDataForUpload = (fileData) => { const fileName = fileData.fileName || fileData.name || 'chat_attachment'; const fileResult = { name: FileUtils.cleanFileName(fileName), @@ -86,141 +80,53 @@ function getDataForUpload(fileData) { fileResult.size = stats.size; return fileResult; }); -} +}; /** * This component renders a function as a child and * returns a "show attachment picker" method that takes * a callback. This is the ios/android implementation * opening a modal with attachment options + * @param {propTypes} props + * @returns {JSX.Element} */ -class AttachmentPicker extends Component { - constructor(...args) { - super(...args); +function AttachmentPicker({type, children}) { + const [isVisible, setIsVisible] = useState(false); - this.state = { - isVisible: false, - focusedIndex: -1, - }; + const completeAttachmentSelection = useRef(); + const onModalHide = useRef(); + const onCanceled = useRef(); - this.menuItemData = [ - { - icon: Expensicons.Camera, - textTranslationKey: 'attachmentPicker.takePhoto', - pickAttachment: () => this.showImagePicker(launchCamera), - }, - { - icon: Expensicons.Gallery, - textTranslationKey: 'attachmentPicker.chooseFromGallery', - pickAttachment: () => this.showImagePicker(launchImageLibrary), - }, - ]; - - // When selecting an image on a native device, it would be redundant to have a second option for choosing a document, - // so it is excluded in this case. - if (this.props.type !== CONST.ATTACHMENT_PICKER_TYPE.IMAGE) { - this.menuItemData.push({ - icon: Expensicons.Paperclip, - textTranslationKey: 'attachmentPicker.chooseDocument', - pickAttachment: () => this.showDocumentPicker(), - }); - } - - this.close = this.close.bind(this); - this.pickAttachment = this.pickAttachment.bind(this); - this.removeKeyboardListener = this.removeKeyboardListener.bind(this); - this.attachKeyboardListener = this.attachKeyboardListener.bind(this); - } - - componentDidUpdate(prevState) { - if (this.state.isVisible === prevState.isVisible) { - return; - } - - if (this.state.isVisible) { - this.attachKeyboardListener(); - } else { - this.removeKeyboardListener(); - } - } - - componentWillUnmount() { - this.removeKeyboardListener(); - } - - attachKeyboardListener() { - const shortcutConfig = CONST.KEYBOARD_SHORTCUTS.ENTER; - this.unsubscribeEnterKey = KeyboardShortcut.subscribe( - shortcutConfig.shortcutKey, - () => { - if (this.state.focusedIndex === -1) { - return; - } - this.selectItem(this.menuItemData[this.state.focusedIndex]); - this.setState({focusedIndex: -1}); // Reset the focusedIndex on selecting any menu - }, - shortcutConfig.descriptionKey, - shortcutConfig.modifiers, - true, - ); - } - - removeKeyboardListener() { - if (!this.unsubscribeEnterKey) { - return; - } - this.unsubscribeEnterKey(); - } - - /** - * Handles the image/document picker result and - * sends the selected attachment to the caller (parent component) - * - * @param {Array} attachments - * @returns {Promise} - */ - pickAttachment(attachments = []) { - if (attachments.length === 0) { - return; - } - - const fileData = _.first(attachments); - - if (fileData.width === -1 || fileData.height === -1) { - this.showImageCorruptionAlert(); - return; - } - - return getDataForUpload(fileData) - .then((result) => { - this.completeAttachmentSelection(result); - }) - .catch((error) => { - this.showGeneralAlert(error.message); - throw error; - }); - } + const {translate} = useLocalize(); + const {isSmallScreenWidth} = useWindowDimensions(); /** * Inform the users when they need to grant camera access and guide them to settings */ - showPermissionsAlert() { + const showPermissionsAlert = useCallback(() => { Alert.alert( - this.props.translate('attachmentPicker.cameraPermissionRequired'), - this.props.translate('attachmentPicker.expensifyDoesntHaveAccessToCamera'), + translate('attachmentPicker.cameraPermissionRequired'), + translate('attachmentPicker.expensifyDoesntHaveAccessToCamera'), [ { - text: this.props.translate('common.cancel'), + text: translate('common.cancel'), style: 'cancel', }, { - text: this.props.translate('common.settings'), + text: translate('common.settings'), onPress: () => Linking.openSettings(), }, ], {cancelable: false}, ); - } + }, [translate]); + + /** + * A generic handling when we don't know the exact reason for an error + */ + const showGeneralAlert = useCallback(() => { + Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingAttachment')); + }, [translate]); /** * Common image picker handling @@ -228,89 +134,136 @@ class AttachmentPicker extends Component { * @param {function} imagePickerFunc - RNImagePicker.launchCamera or RNImagePicker.launchImageLibrary * @returns {Promise} */ - showImagePicker(imagePickerFunc) { - return new Promise((resolve, reject) => { - imagePickerFunc(getImagePickerOptions(this.props.type), (response) => { - if (response.didCancel) { - // When the user cancelled resolve with no attachment - return resolve(); - } - if (response.errorCode) { - switch (response.errorCode) { - case 'permission': - this.showPermissionsAlert(); - return resolve(); - default: - this.showGeneralAlert(); - break; + const showImagePicker = useCallback( + (imagePickerFunc) => + new Promise((resolve, reject) => { + imagePickerFunc(getImagePickerOptions(type), (response) => { + if (response.didCancel) { + // When the user cancelled resolve with no attachment + return resolve(); + } + if (response.errorCode) { + switch (response.errorCode) { + case 'permission': + showPermissionsAlert(); + return resolve(); + default: + showGeneralAlert(); + break; + } + + return reject(new Error(`Error during attachment selection: ${response.errorMessage}`)); } - return reject(new Error(`Error during attachment selection: ${response.errorMessage}`)); - } - - return resolve(response.assets); - }); - }); - } + return resolve(response.assets); + }); + }), + [showGeneralAlert, showPermissionsAlert, type], + ); /** - * A generic handling when we don't know the exact reason for an error + * Launch the DocumentPicker. Results are in the same format as ImagePicker * + * @returns {Promise} */ - showGeneralAlert() { - Alert.alert(this.props.translate('attachmentPicker.attachmentError'), this.props.translate('attachmentPicker.errorWhileSelectingAttachment')); - } + const showDocumentPicker = useCallback( + () => + RNDocumentPicker.pick(documentPickerOptions).catch((error) => { + if (RNDocumentPicker.isCancel(error)) { + return; + } + + showGeneralAlert(error.message); + throw error; + }), + [showGeneralAlert], + ); + + const menuItemData = useMemo(() => { + const data = [ + { + icon: Expensicons.Camera, + textTranslationKey: 'attachmentPicker.takePhoto', + pickAttachment: () => showImagePicker(launchCamera), + }, + { + icon: Expensicons.Gallery, + textTranslationKey: 'attachmentPicker.chooseFromGallery', + pickAttachment: () => showImagePicker(launchImageLibrary), + }, + ]; + + if (type !== CONST.ATTACHMENT_PICKER_TYPE.IMAGE) { + data.push({ + icon: Expensicons.Paperclip, + textTranslationKey: 'attachmentPicker.chooseDocument', + pickAttachment: showDocumentPicker, + }); + } + + return data; + }, [showDocumentPicker, showImagePicker, type]); + + const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({initialFocusedIndex: -1, maxIndex: menuItemData.length - 1, isActive: isVisible}); /** * An attachment error dialog when user selected malformed images */ - showImageCorruptionAlert() { - Alert.alert(this.props.translate('attachmentPicker.attachmentError'), this.props.translate('attachmentPicker.errorWhileSelectingCorruptedImage')); - } + const showImageCorruptionAlert = useCallback(() => { + Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingCorruptedImage')); + }, [translate]); /** - * Launch the DocumentPicker. Results are in the same format as ImagePicker + * Opens the attachment modal * - * @returns {Promise} + * @param {function} onPickedHandler A callback that will be called with the selected attachment + * @param {function} onCanceledHandler A callback that will be called without a selected attachment */ - showDocumentPicker() { - return RNDocumentPicker.pick(documentPickerOptions).catch((error) => { - if (RNDocumentPicker.isCancel(error)) { - return; - } - - this.showGeneralAlert(error.message); - throw error; - }); - } + const open = (onPickedHandler, onCanceledHandler = () => {}) => { + completeAttachmentSelection.current = onPickedHandler; + onCanceled.current = onCanceledHandler; + setIsVisible(true); + }; /** - * Triggers the `onPicked` callback with the selected attachment + * Closes the attachment modal */ - completeAttachmentSelection() { - if (!this.state.result) { - return; - } - - this.state.onPicked(this.state.result); - } + const close = () => { + setIsVisible(false); + }; /** - * Opens the attachment modal + * Handles the image/document picker result and + * sends the selected attachment to the caller (parent component) * - * @param {function} onPicked A callback that will be called with the selected attachment + * @param {Array} attachments + * @returns {Promise} */ - open(onPicked) { - this.completeAttachmentSelection = onPicked; - this.setState({isVisible: true}); - } + const pickAttachment = useCallback( + (attachments = []) => { + if (attachments.length === 0) { + onCanceled.current(); + return Promise.resolve(); + } - /** - * Closes the attachment modal - */ - close() { - this.setState({isVisible: false}); - } + const fileData = _.first(attachments); + + if (fileData.width === -1 || fileData.height === -1) { + showImageCorruptionAlert(); + return Promise.resolve(); + } + + return getDataForUpload(fileData) + .then((result) => { + completeAttachmentSelection.current(result); + }) + .catch((error) => { + showGeneralAlert(error.message); + throw error; + }); + }, + [showGeneralAlert, showImageCorruptionAlert], + ); /** * Setup native attachment selection to start after this popover closes @@ -318,68 +271,80 @@ class AttachmentPicker extends Component { * @param {Object} item - an item from this.menuItemData * @param {Function} item.pickAttachment */ - selectItem(item) { - /* setTimeout delays execution to the frame after the modal closes - * without this on iOS closing the modal closes the gallery/camera as well */ - this.onModalHide = () => - setTimeout( - () => - item - .pickAttachment() - .then(this.pickAttachment) - .catch(console.error) - .finally(() => delete this.onModalHide), - 200, - ); - - this.close(); - } + const selectItem = useCallback( + (item) => { + /* setTimeout delays execution to the frame after the modal closes + * without this on iOS closing the modal closes the gallery/camera as well */ + onModalHide.current = () => + setTimeout( + () => + item + .pickAttachment() + .then(pickAttachment) + .catch(console.error) + .finally(() => delete onModalHide.current), + 200, + ); + + close(); + }, + [pickAttachment], + ); + + useKeyboardShortcut( + CONST.KEYBOARD_SHORTCUTS.ENTER, + () => { + if (focusedIndex === -1) { + return; + } + selectItem(menuItemData[focusedIndex]); + setFocusedIndex(-1); // Reset the focusedIndex on selecting any menu + }, + { + isActive: isVisible, + }, + ); /** * Call the `children` renderProp with the interface defined in propTypes * * @returns {React.ReactNode} */ - renderChildren() { - return this.props.children({ - openPicker: ({onPicked}) => this.open(onPicked), + const renderChildren = () => + children({ + openPicker: ({onPicked, onCanceled: newOnCanceled}) => open(onPicked, newOnCanceled), }); - } - render() { - return ( - <> - - - this.setState({focusedIndex: index})} - > - {_.map(this.menuItemData, (item, menuIndex) => ( - this.selectItem(item)} - focused={this.state.focusedIndex === menuIndex} - /> - ))} - - - - {this.renderChildren()} - - ); - } + return ( + <> + { + close(); + onCanceled.current(); + }} + isVisible={isVisible} + anchorPosition={styles.createMenuPosition} + onModalHide={onModalHide.current} + > + + {_.map(menuItemData, (item, menuIndex) => ( + selectItem(item)} + focused={focusedIndex === menuIndex} + /> + ))} + + + {renderChildren()} + + ); } AttachmentPicker.propTypes = propTypes; AttachmentPicker.defaultProps = defaultProps; +AttachmentPicker.displayName = 'AttachmentPicker'; -export default compose(withWindowDimensions, withLocalize)(AttachmentPicker); +export default AttachmentPicker; diff --git a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js index e19f7617bb28..d33659fd04ae 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js @@ -40,7 +40,7 @@ function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward const isForwardDisabled = page === _.size(attachments) - 1; const {translate} = useLocalize(); - const {isSmallScreenWidth} = useWindowDimensions; + const {isSmallScreenWidth} = useWindowDimensions(); return shouldShowArrows ? ( <> diff --git a/src/components/Attachments/AttachmentCarousel/attachmentCarouselPropTypes.js b/src/components/Attachments/AttachmentCarousel/attachmentCarouselPropTypes.js index a63b0f23d1ab..81f22f684243 100644 --- a/src/components/Attachments/AttachmentCarousel/attachmentCarouselPropTypes.js +++ b/src/components/Attachments/AttachmentCarousel/attachmentCarouselPropTypes.js @@ -12,6 +12,9 @@ const propTypes = { /** Callback to close carousel when user swipes down (on native) */ onClose: PropTypes.func, + /** Function to change the download button Visibility */ + setDownloadButtonVisibility: PropTypes.func, + /** Object of report actions for this report */ reportActions: PropTypes.shape(reportActionPropTypes), @@ -24,6 +27,7 @@ const defaultProps = { reportActions: {}, onNavigate: () => {}, onClose: () => {}, + setDownloadButtonVisibility: () => {}, }; export {propTypes, defaultProps}; diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index 047a016674b7..b967d5ab0066 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -1,20 +1,21 @@ import {Parser as HtmlParser} from 'htmlparser2'; import _ from 'underscore'; +import lodashGet from 'lodash/get'; import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; +import * as TransactionUtils from '../../../libs/TransactionUtils'; +import * as ReceiptUtils from '../../../libs/ReceiptUtils'; import CONST from '../../../CONST'; import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot'; -import Navigation from '../../../libs/Navigation/Navigation'; /** * Constructs the initial component state from report actions * @param {Object} report * @param {Array} reportActions - * @param {String} source - * @returns {{attachments: Array, initialPage: Number, initialItem: Object, initialActiveSource: String}} + * @returns {Array} */ -function extractAttachmentsFromReport(report, reportActions, source) { +function extractAttachmentsFromReport(report, reportActions) { const actions = [ReportActionsUtils.getParentReportAction(report), ...ReportActionsUtils.getSortedReportActions(_.values(reportActions))]; - let attachments = []; + const attachments = []; const htmlParser = new HtmlParser({ onopentag: (name, attribs) => { @@ -30,6 +31,7 @@ function extractAttachmentsFromReport(report, reportActions, source) { source: tryResolveUrlFromApiRoot(expensifySource || attribs.src), isAuthTokenRequired: Boolean(expensifySource), file: {name: attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE]}, + isReceipt: false, }); }, }); @@ -38,31 +40,33 @@ function extractAttachmentsFromReport(report, reportActions, source) { if (!ReportActionsUtils.shouldReportActionBeVisible(action, key)) { return; } - htmlParser.write(_.get(action, ['message', 0, 'html'])); - }); - htmlParser.end(); - attachments = attachments.reverse(); + // We're handling receipts differently here because receipt images are not + // part of the report action message, the images are constructed client-side + if (ReportActionsUtils.isMoneyRequestAction(action)) { + const transactionID = lodashGet(action, ['originalMessage', 'IOUTransactionID']); + if (!transactionID) { + return; + } - const initialPage = _.findIndex(attachments, (a) => a.source === source); - if (initialPage === -1) { - Navigation.dismissModal(); - return { - attachments: [], - initialPage: 0, - initialItem: undefined, - initialActiveSource: null, - }; - } + const transaction = TransactionUtils.getTransaction(transactionID); + if (TransactionUtils.hasReceipt(transaction)) { + const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); + attachments.unshift({ + source: tryResolveUrlFromApiRoot(image), + isAuthTokenRequired: true, + file: {name: transaction.filename}, + isReceipt: true, + }); + return; + } + } - const initialItem = attachments[initialPage]; + htmlParser.write(_.get(action, ['message', 0, 'html'])); + }); + htmlParser.end(); - return { - attachments, - initialPage, - initialItem, - initialActiveSource: initialItem.source, - }; + return attachments.reverse(); } export default extractAttachmentsFromReport; diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js index 564e60b65dd1..cec5f54508cb 100644 --- a/src/components/Attachments/AttachmentCarousel/index.js +++ b/src/components/Attachments/AttachmentCarousel/index.js @@ -1,4 +1,4 @@ -import React, {useRef, useCallback, useState, useEffect, useMemo} from 'react'; +import React, {useRef, useCallback, useState, useEffect} from 'react'; import {View, FlatList, PixelRatio, Keyboard} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -15,6 +15,10 @@ import withLocalize from '../../withLocalize'; import compose from '../../../libs/compose'; import useCarouselArrows from './useCarouselArrows'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; +import Navigation from '../../../libs/Navigation/Navigation'; +import BlockingView from '../../BlockingViews/BlockingView'; +import * as Illustrations from '../../Icon/Illustrations'; +import variables from '../../../styles/variables'; const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); const viewabilityConfig = { @@ -23,24 +27,37 @@ const viewabilityConfig = { itemVisiblePercentThreshold: 95, }; -function AttachmentCarousel({report, reportActions, source, onNavigate}) { +function AttachmentCarousel({report, reportActions, source, onNavigate, setDownloadButtonVisibility, translate}) { const scrollRef = useRef(null); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); - const {attachments, initialPage, initialActiveSource, initialItem} = useMemo(() => extractAttachmentsFromReport(report, reportActions, source), [report, reportActions, source]); + const [containerWidth, setContainerWidth] = useState(0); + const [page, setPage] = useState(0); + const [attachments, setAttachments] = useState([]); + const [activeSource, setActiveSource] = useState(source); + const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); useEffect(() => { - // Update the parent modal's state with the source and name from the mapped attachments - if (!initialItem) return; - onNavigate(initialItem); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [initialItem]); + const attachmentsFromReport = extractAttachmentsFromReport(report, reportActions); - const [containerWidth, setContainerWidth] = useState(0); - const [page, setPage] = useState(initialPage); - const [activeSource, setActiveSource] = useState(initialActiveSource); - const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); + const initialPage = _.findIndex(attachmentsFromReport, (a) => a.source === source); + + // Dismiss the modal when deleting an attachment during its display in preview. + if (initialPage === -1 && _.find(attachments, (a) => a.source === source)) { + Navigation.dismissModal(); + } else { + setPage(initialPage); + setAttachments(attachmentsFromReport); + + // Update the download button visibility in the parent modal + setDownloadButtonVisibility(initialPage !== -1); + + // Update the parent modal's state with the source and name from the mapped attachments + if (!_.isUndefined(attachmentsFromReport[initialPage])) onNavigate(attachmentsFromReport[initialPage]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [report, reportActions, source]); /** * Updates the page state when the user navigates between attachments @@ -153,49 +170,60 @@ function AttachmentCarousel({report, reportActions, source, onNavigate}) { onMouseEnter={() => !canUseTouchScreen && setShouldShowArrows(true)} onMouseLeave={() => !canUseTouchScreen && setShouldShowArrows(false)} > - cycleThroughAttachments(-1)} - onForward={() => cycleThroughAttachments(1)} - autoHideArrow={autoHideArrows} - cancelAutoHideArrow={cancelAutoHideArrows} - /> - - {containerWidth > 0 && ( - item.source} - viewabilityConfig={viewabilityConfig} - onViewableItemsChanged={updatePage.current} + {page === -1 ? ( + + ) : ( + <> + cycleThroughAttachments(-1)} + onForward={() => cycleThroughAttachments(1)} + autoHideArrow={autoHideArrows} + cancelAutoHideArrow={cancelAutoHideArrows} + /> + + {containerWidth > 0 && ( + item.source} + viewabilityConfig={viewabilityConfig} + onViewableItemsChanged={updatePage.current} + /> + )} + + + )} - - ); } diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js index 58e248d514e1..4162cfae88e9 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.js +++ b/src/components/Attachments/AttachmentCarousel/index.native.js @@ -1,6 +1,7 @@ -import React, {useCallback, useEffect, useRef, useState, useMemo} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View, Keyboard, PixelRatio} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import AttachmentCarouselPager from './Pager'; import styles from '../../../styles/styles'; import CarouselButtons from './CarouselButtons'; @@ -9,24 +10,44 @@ import ONYXKEYS from '../../../ONYXKEYS'; import {propTypes, defaultProps} from './attachmentCarouselPropTypes'; import extractAttachmentsFromReport from './extractAttachmentsFromReport'; import useCarouselArrows from './useCarouselArrows'; +import Navigation from '../../../libs/Navigation/Navigation'; +import BlockingView from '../../BlockingViews/BlockingView'; +import * as Illustrations from '../../Icon/Illustrations'; +import variables from '../../../styles/variables'; +import compose from '../../../libs/compose'; +import withLocalize from '../../withLocalize'; -function AttachmentCarousel({report, reportActions, source, onNavigate, onClose}) { - const {attachments, initialPage, initialActiveSource, initialItem} = useMemo(() => extractAttachmentsFromReport(report, reportActions, source), [report, reportActions, source]); - - useEffect(() => { - // Update the parent modal's state with the source and name from the mapped attachments - onNavigate(initialItem); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [initialItem]); - +function AttachmentCarousel({report, reportActions, source, onNavigate, onClose, setDownloadButtonVisibility, translate}) { const pagerRef = useRef(null); const [containerDimensions, setContainerDimensions] = useState({width: 0, height: 0}); - const [page, setPage] = useState(initialPage); - const [activeSource, setActiveSource] = useState(initialActiveSource); + const [page, setPage] = useState(0); + const [attachments, setAttachments] = useState([]); + const [activeSource, setActiveSource] = useState(source); const [isPinchGestureRunning, setIsPinchGestureRunning] = useState(true); const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); + useEffect(() => { + const attachmentsFromReport = extractAttachmentsFromReport(report, reportActions); + + const initialPage = _.findIndex(attachmentsFromReport, (a) => a.source === source); + + // Dismiss the modal when deleting an attachment during its display in preview. + if (initialPage === -1 && _.find(attachments, (a) => a.source === source)) { + Navigation.dismissModal(); + } else { + setPage(initialPage); + setAttachments(attachmentsFromReport); + + // Update the download button visibility in the parent modal + setDownloadButtonVisibility(initialPage !== -1); + + // Update the parent modal's state with the source and name from the mapped attachments + if (!_.isUndefined(attachmentsFromReport[initialPage])) onNavigate(attachmentsFromReport[initialPage]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [report, reportActions, source]); + /** * Updates the page state when the user navigates between attachments * @param {Object} item @@ -90,31 +111,42 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose} onMouseEnter={() => setShouldShowArrows(true)} onMouseLeave={() => setShouldShowArrows(false)} > - cycleThroughAttachments(-1)} - onForward={() => cycleThroughAttachments(1)} - autoHideArrow={autoHideArrows} - cancelAutoHideArrow={cancelAutoHideArrows} - /> - - {containerDimensions.width > 0 && containerDimensions.height > 0 && ( - updatePage(newPage)} - onPinchGestureChange={(newIsPinchGestureRunning) => { - setIsPinchGestureRunning(newIsPinchGestureRunning); - if (!newIsPinchGestureRunning && !shouldShowArrows) setShouldShowArrows(true); - }} - onSwipeDown={onClose} - containerWidth={containerDimensions.width} - containerHeight={containerDimensions.height} - ref={pagerRef} + {page === -1 ? ( + + ) : ( + <> + cycleThroughAttachments(-1)} + onForward={() => cycleThroughAttachments(1)} + autoHideArrow={autoHideArrows} + cancelAutoHideArrow={cancelAutoHideArrows} + /> + + {containerDimensions.width > 0 && containerDimensions.height > 0 && ( + updatePage(newPage)} + onPinchGestureChange={(newIsPinchGestureRunning) => { + setIsPinchGestureRunning(newIsPinchGestureRunning); + if (!newIsPinchGestureRunning && !shouldShowArrows) setShouldShowArrows(true); + }} + onSwipeDown={onClose} + containerWidth={containerDimensions.width} + containerHeight={containerDimensions.height} + ref={pagerRef} + /> + )} + )} ); @@ -122,9 +154,12 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose} AttachmentCarousel.propTypes = propTypes; AttachmentCarousel.defaultProps = defaultProps; -export default withOnyx({ - reportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, - canEvict: false, - }, -})(AttachmentCarousel); +export default compose( + withOnyx({ + reportActions: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + canEvict: false, + }, + }), + withLocalize, +)(AttachmentCarousel); diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.native.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.native.js index dc329a9fd3fd..bf777f41945e 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.native.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.native.js @@ -1,4 +1,4 @@ -import React, {memo, useCallback, useContext} from 'react'; +import React, {memo, useCallback, useContext, useEffect} from 'react'; import styles from '../../../../styles/styles'; import {attachmentViewPdfPropTypes, attachmentViewPdfDefaultProps} from './propTypes'; import PDFView from '../../../PDFView'; @@ -7,6 +7,11 @@ import AttachmentCarouselPagerContext from '../../AttachmentCarousel/Pager/Attac function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, isUsedInCarousel, onPress, onScaleChanged: onScaleChangedProp, onToggleKeyboard, onLoadComplete}) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); + useEffect(() => { + attachmentCarouselPagerContext.onPinchGestureChange(false); + // eslint-disable-next-line react-hooks/exhaustive-deps -- we just want to call this function when component is mounted + }, []); + const onScaleChanged = useCallback( (scale) => { onScaleChangedProp(); @@ -15,6 +20,8 @@ function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, isUsedInCarouse if (isUsedInCarousel) { const shouldPagerScroll = scale === 1; + attachmentCarouselPagerContext.onPinchGestureChange(!shouldPagerScroll); + if (attachmentCarouselPagerContext.shouldPagerScroll.value === shouldPagerScroll) return; attachmentCarouselPagerContext.shouldPagerScroll.value = shouldPagerScroll; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 3ad643d34bcd..47353d915060 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -15,7 +15,7 @@ import variables from '../../../styles/variables'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; import addEncryptedAuthTokenToURL from '../../../libs/addEncryptedAuthTokenToURL'; - +import * as StyleUtils from '../../../styles/StyleUtils'; import {attachmentViewPropTypes, attachmentViewDefaultProps} from './propTypes'; const propTypes = { @@ -34,6 +34,9 @@ const propTypes = { /** Extra styles to pass to View wrapper */ // eslint-disable-next-line react/forbid-prop-types containerStyles: PropTypes.arrayOf(PropTypes.object), + + /** Denotes whether it is a workspace avatar or not */ + isWorkspaceAvatar: PropTypes.bool, }; const defaultProps = { @@ -42,6 +45,7 @@ const defaultProps = { shouldShowLoadingSpinnerIcon: false, onToggleKeyboard: () => {}, containerStyles: [], + isWorkspaceAvatar: false, }; function AttachmentView({ @@ -57,16 +61,27 @@ function AttachmentView({ onToggleKeyboard, translate, isFocused, + isWorkspaceAvatar, }) { const [loadComplete, setLoadComplete] = useState(false); // Handles case where source is a component (ex: SVG) if (_.isFunction(source)) { + let iconFillColor = ''; + let additionalStyles = []; + if (isWorkspaceAvatar) { + const defaultWorkspaceAvatarColor = StyleUtils.getDefaultWorkspaceAvatarColor(file.name); + iconFillColor = defaultWorkspaceAvatarColor.fill; + additionalStyles = [defaultWorkspaceAvatarColor]; + } + return ( ); } diff --git a/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js b/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js index 6ff330d839c6..ad3e2babb1cc 100644 --- a/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js +++ b/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import refPropType from '../refPropTypes'; const propTypes = { /** Array of suggestions */ @@ -27,8 +28,17 @@ const propTypes = { /** create accessibility label for each item */ accessibilityLabelExtractor: PropTypes.func.isRequired, + + /** Ref of the container enclosing the menu. + * This is needed to render the menu in correct position inside a portal + */ + parentContainerRef: refPropType, }; -const defaultProps = {}; +const defaultProps = { + parentContainerRef: { + current: null, + }, +}; export {propTypes, defaultProps}; diff --git a/src/components/AutoCompleteSuggestions/index.js b/src/components/AutoCompleteSuggestions/index.js index 9e1951d9a1d5..b37fcd7181d9 100644 --- a/src/components/AutoCompleteSuggestions/index.js +++ b/src/components/AutoCompleteSuggestions/index.js @@ -1,7 +1,11 @@ import React from 'react'; +import {View} from 'react-native'; +import ReactDOM from 'react-dom'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; import {propTypes} from './autoCompleteSuggestionsPropTypes'; +import * as StyleUtils from '../../styles/StyleUtils'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; /** * On the mobile-web platform, when long-pressing on auto-complete suggestions, @@ -10,8 +14,14 @@ import {propTypes} from './autoCompleteSuggestionsPropTypes'; * On the native platform, tapping on auto-complete suggestions will not blur the main input. */ -function AutoCompleteSuggestions(props) { +function AutoCompleteSuggestions({parentContainerRef, ...props}) { const containerRef = React.useRef(null); + const {windowHeight, windowWidth} = useWindowDimensions(); + const [{width, left, bottom}, setContainerState] = React.useState({ + width: 0, + left: 0, + bottom: 0, + }); React.useEffect(() => { const container = containerRef.current; if (!container) { @@ -26,13 +36,26 @@ function AutoCompleteSuggestions(props) { return () => (container.onpointerdown = null); }, []); - return ( + React.useEffect(() => { + if (!parentContainerRef || !parentContainerRef.current) { + return; + } + parentContainerRef.current.measureInWindow((x, y, w) => setContainerState({left: x, bottom: windowHeight - y, width: w})); + }, [parentContainerRef, windowHeight, windowWidth]); + + const componentToRender = ( ); + + if (!width) { + return componentToRender; + } + + return ReactDOM.createPortal({componentToRender}, document.querySelector('body')); } AutoCompleteSuggestions.propTypes = propTypes; diff --git a/src/components/AutoCompleteSuggestions/index.native.js b/src/components/AutoCompleteSuggestions/index.native.js index 22af774bd4fc..514cec6cd844 100644 --- a/src/components/AutoCompleteSuggestions/index.native.js +++ b/src/components/AutoCompleteSuggestions/index.native.js @@ -2,7 +2,7 @@ import React from 'react'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import {propTypes} from './autoCompleteSuggestionsPropTypes'; -function AutoCompleteSuggestions(props) { +function AutoCompleteSuggestions({parentContainerRef, ...props}) { // eslint-disable-next-line react/jsx-props-no-spreading return ; } diff --git a/src/components/AutoUpdateTime.js b/src/components/AutoUpdateTime.js index a522a3e6dcdc..cb15cb20b4ea 100644 --- a/src/components/AutoUpdateTime.js +++ b/src/components/AutoUpdateTime.js @@ -27,21 +27,13 @@ function AutoUpdateTime(props) { * @returns {moment} Returns the locale moment object */ const getCurrentUserLocalTime = useCallback( - () => DateUtils.getLocalMomentFromDatetime(props.preferredLocale, null, props.timezone.selected), + () => DateUtils.getLocalDateFromDatetime(props.preferredLocale, null, props.timezone.selected), [props.preferredLocale, props.timezone.selected], ); const [currentUserLocalTime, setCurrentUserLocalTime] = useState(getCurrentUserLocalTime); const minuteRef = useRef(new Date().getMinutes()); - const timezoneName = useMemo(() => { - // With non-GMT timezone, moment.zoneAbbr() will return the name of that timezone, so we can use it directly. - if (Number.isNaN(Number(currentUserLocalTime.zoneAbbr()))) { - return currentUserLocalTime.zoneAbbr(); - } - - // With GMT timezone, moment.zoneAbbr() will return a number, so we need to display it as GMT {abbreviations} format, e.g.: GMT +07 - return `GMT ${currentUserLocalTime.zoneAbbr()}`; - }, [currentUserLocalTime]); + const timezoneName = useMemo(() => DateUtils.getZoneAbbreviation(currentUserLocalTime, props.timezone.selected), [currentUserLocalTime, props.timezone.selected]); useEffect(() => { // If the any of the props that getCurrentUserLocalTime depends on change, we want to update the displayed time immediately @@ -68,7 +60,7 @@ function AutoUpdateTime(props) { {props.translate('detailsPage.localTime')} - {currentUserLocalTime.format('LT')} {timezoneName} + {DateUtils.formatToLocalTime(currentUserLocalTime)} {timezoneName} ); diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index 99262bf12938..baa958106f84 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -22,6 +22,7 @@ import HeaderGap from '../HeaderGap'; import * as StyleUtils from '../../styles/StyleUtils'; import Tooltip from '../Tooltip'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; +import ScreenWrapper from '../ScreenWrapper'; const propTypes = { /** Link to image for cropping */ @@ -361,79 +362,85 @@ function AvatarCropModal(props) { type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED} onModalHide={resetState} > - {props.isSmallScreenWidth && } - - {props.translate('avatarCropModal.description')} - - {/* To avoid layout shift we should hide this component until the image container & image is initialized */} - {!isImageInitialized || !isImageContainerInitialized ? ( - - ) : ( - <> - } + + {props.translate('avatarCropModal.description')} + + {/* To avoid layout shift we should hide this component until the image container & image is initialized */} + {!isImageInitialized || !isImageContainerInitialized ? ( + - - + - runOnUI(sliderOnPress)(e.nativeEvent.locationX)} - accessibilityLabel="slider" - accessibilityRole={CONST.ACCESSIBILITY_ROLE.ADJUSTABLE} - > - + - - - - ) : ( @@ -122,6 +145,9 @@ function ButtonWithDropdownMenu(props) { text={selectedItem.text} onPress={(event) => props.onPress(event, props.options[0].value)} pressOnEnter + large={isButtonSizeLarge} + medium={!isButtonSizeLarge} + innerStyles={[innerStyleDropButton]} /> )} {props.options.length > 1 && !_.isEmpty(popoverAnchorPosition) && ( diff --git a/src/components/CategoryPicker/categoryPickerPropTypes.js b/src/components/CategoryPicker/categoryPickerPropTypes.js new file mode 100644 index 000000000000..ccc1643021ce --- /dev/null +++ b/src/components/CategoryPicker/categoryPickerPropTypes.js @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types'; +import categoryPropTypes from '../categoryPropTypes'; + +const propTypes = { + /** The report ID of the IOU */ + reportID: PropTypes.string.isRequired, + + /** The policyID we are getting categories for */ + policyID: PropTypes.string, + + /** The type of IOU report, i.e. bill, request, send */ + iouType: PropTypes.string.isRequired, + + /* Onyx Props */ + /** Collection of categories attached to a policy */ + policyCategories: PropTypes.objectOf(categoryPropTypes), +}; + +const defaultProps = { + policyID: '', + policyCategories: null, +}; + +export {propTypes, defaultProps}; diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js new file mode 100644 index 000000000000..163ab6673ca2 --- /dev/null +++ b/src/components/CategoryPicker/index.js @@ -0,0 +1,56 @@ +import React, {useMemo} from 'react'; +import _ from 'underscore'; +import {withOnyx} from 'react-native-onyx'; +import ONYXKEYS from '../../ONYXKEYS'; +import {propTypes, defaultProps} from './categoryPickerPropTypes'; +import OptionsList from '../OptionsList'; +import styles from '../../styles/styles'; +import ScreenWrapper from '../ScreenWrapper'; +import Navigation from '../../libs/Navigation/Navigation'; +import ROUTES from '../../ROUTES'; + +function CategoryPicker({policyCategories, reportID, iouType}) { + const sections = useMemo(() => { + const categoryList = _.chain(policyCategories) + .values() + .map((category) => ({ + text: category.name, + keyForList: category.name, + tooltipText: category.name, + })) + .value(); + + return [ + { + data: categoryList, + }, + ]; + }, [policyCategories]); + + const navigateBack = () => { + Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); + }; + + return ( + + {({safeAreaPaddingBottomStyle}) => ( + + )} + + ); +} + +CategoryPicker.displayName = 'CategoryPicker'; +CategoryPicker.propTypes = propTypes; +CategoryPicker.defaultProps = defaultProps; + +export default withOnyx({ + policyCategories: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + }, +})(CategoryPicker); diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index 4b4601247008..dc9b5ba4ac67 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -1,8 +1,9 @@ -import React from 'react'; +import React, {useState, useRef, useEffect, useCallback, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {flushSync} from 'react-dom'; import RNTextInput from '../RNTextInput'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import themeColors from '../../styles/themes/default'; @@ -17,6 +18,7 @@ import Text from '../Text'; import isEnterWhileComposition from '../../libs/KeyboardShortcut/isEnterWhileComposition'; import CONST from '../../CONST'; import withNavigation from '../withNavigation'; +import ReportActionComposeFocusManager from '../../libs/ReportActionComposeFocusManager'; const propTypes = { /** Maximum number of lines in the text input */ @@ -72,15 +74,15 @@ const propTypes = { /** Allow the full composer to be opened */ setIsFullComposerAvailable: PropTypes.func, - /** Whether the composer is full size */ - isComposerFullSize: PropTypes.bool, - /** Should we calculate the caret position */ shouldCalculateCaretPosition: PropTypes.bool, /** Function to check whether composer is covered up or not */ checkComposerVisibility: PropTypes.func, + /** Whether this is the report action compose */ + isReportActionCompose: PropTypes.bool, + ...withLocalizePropTypes, ...windowDimensionsPropTypes, @@ -106,198 +108,160 @@ const defaultProps = { }, isFullComposerAvailable: false, setIsFullComposerAvailable: () => {}, - isComposerFullSize: false, shouldCalculateCaretPosition: false, checkComposerVisibility: () => false, + isReportActionCompose: false, }; /** - * Enable Markdown parsing. - * On web we like to have the Text Input field always focused so the user can easily type a new chat + * Retrieves the characters from the specified cursor position up to the next space or new line. + * + * @param {string} str - The input string. + * @param {number} cursorPos - The position of the cursor within the input string. + * @returns {string} - The substring from the cursor position up to the next space or new line. + * If no space or new line is found, returns the substring from the cursor position to the end of the input string. */ -class Composer extends React.Component { - constructor(props) { - super(props); - - const initialValue = props.defaultValue ? `${props.defaultValue}` : `${props.value || ''}`; - - this.state = { - numberOfLines: props.numberOfLines, - selection: { - start: initialValue.length, - end: initialValue.length, - }, - valueBeforeCaret: '', - }; - - this.paste = this.paste.bind(this); - this.handleKeyPress = this.handleKeyPress.bind(this); - this.handlePaste = this.handlePaste.bind(this); - this.handlePastedHTML = this.handlePastedHTML.bind(this); - this.handleWheel = this.handleWheel.bind(this); - this.shouldCallUpdateNumberOfLines = this.shouldCallUpdateNumberOfLines.bind(this); - this.addCursorPositionToSelectionChange = this.addCursorPositionToSelectionChange.bind(this); - this.textRef = React.createRef(null); - this.unsubscribeBlur = () => null; - this.unsubscribeFocus = () => null; - } +const getNextChars = (str, cursorPos) => { + // Get the substring starting from the cursor position + const substr = str.substring(cursorPos); - componentDidMount() { - this.updateNumberOfLines(); + // Find the index of the next space or new line character + const spaceIndex = substr.search(/[ \n]/); - // This callback prop is used by the parent component using the constructor to - // get a ref to the inner textInput element e.g. if we do - // this.textInput = el} /> this will not - // return a ref to the component, but rather the HTML element by default - if (this.props.forwardedRef && _.isFunction(this.props.forwardedRef)) { - this.props.forwardedRef(this.textInput); - } - - // There is no onPaste or onDrag for TextInput in react-native so we will add event - // listeners here and unbind when the component unmounts - if (this.textInput) { - this.textInput.addEventListener('wheel', this.handleWheel); - - // we need to handle listeners on navigation focus/blur as Composer is not unmounting - // when navigating away to different report - this.unsubscribeFocus = this.props.navigation.addListener('focus', () => document.addEventListener('paste', this.handlePaste)); - this.unsubscribeBlur = this.props.navigation.addListener('blur', () => document.removeEventListener('paste', this.handlePaste)); - - // We need to add paste listener manually as well as navigation focus event is not triggered on component mount - document.addEventListener('paste', this.handlePaste); - } + if (spaceIndex === -1) { + return substr; } - componentDidUpdate(prevProps) { - if (!prevProps.shouldClear && this.props.shouldClear) { - this.textInput.clear(); - // eslint-disable-next-line react/no-did-update-set-state - this.setState({numberOfLines: 1}); - this.props.onClear(); - } - - if ( - prevProps.value !== this.props.value || - prevProps.defaultValue !== this.props.defaultValue || - prevProps.isComposerFullSize !== this.props.isComposerFullSize || - prevProps.windowWidth !== this.props.windowWidth || - prevProps.numberOfLines !== this.props.numberOfLines - ) { - this.updateNumberOfLines(); - } - - if (prevProps.selection !== this.props.selection) { - // eslint-disable-next-line react/no-did-update-set-state - this.setState({selection: this.props.selection}); - } - } + // If there is a space or new line, return the substring up to the space or new line + return substr.substring(0, spaceIndex); +}; - componentWillUnmount() { - if (!this.textInput) { +// Enable Markdown parsing. +// On web we like to have the Text Input field always focused so the user can easily type a new chat +function Composer({ + value, + defaultValue, + maxLines, + onKeyPress, + style, + shouldClear, + autoFocus, + translate, + isFullComposerAvailable, + shouldCalculateCaretPosition, + numberOfLines: numberOfLinesProp, + isDisabled, + forwardedRef, + navigation, + onClear, + onPasteFile, + onSelectionChange, + onNumberOfLinesChange, + setIsFullComposerAvailable, + checkComposerVisibility, + selection: selectionProp, + isReportActionCompose, + ...props +}) { + const textRef = useRef(null); + const textInput = useRef(null); + const initialValue = defaultValue ? `${defaultValue}` : `${value || ''}`; + const [numberOfLines, setNumberOfLines] = useState(numberOfLinesProp); + const [selection, setSelection] = useState({ + start: initialValue.length, + end: initialValue.length, + }); + const [caretContent, setCaretContent] = useState(''); + const [valueBeforeCaret, setValueBeforeCaret] = useState(''); + const [textInputWidth, setTextInputWidth] = useState(''); + + useEffect(() => { + if (!shouldClear) { return; } - - document.removeEventListener('paste', this.handlePaste); - this.unsubscribeFocus(); - this.unsubscribeBlur(); - this.textInput.removeEventListener('wheel', this.handleWheel); - } - - // Get characters from the cursor to the next space or new line - getNextChars(str, cursorPos) { - // Get the substring starting from the cursor position - const substr = str.substring(cursorPos); - - // Find the index of the next space or new line character - const spaceIndex = substr.search(/[ \n]/); - - if (spaceIndex === -1) { - return substr; - } - - // If there is a space or new line, return the substring up to the space or new line - return substr.substring(0, spaceIndex); - } + textInput.current.clear(); + setNumberOfLines(1); + onClear(); + }, [shouldClear, onClear]); + + useEffect(() => { + setSelection((prevSelection) => { + if (!!prevSelection && selectionProp.start === prevSelection.start && selectionProp.end === prevSelection.end) { + return; + } + return selectionProp; + }); + }, [selectionProp]); /** * Adds the cursor position to the selection change event. * * @param {Event} event */ - addCursorPositionToSelectionChange(event) { - if (this.props.shouldCalculateCaretPosition) { - const newValueBeforeCaret = event.target.value.slice(0, event.nativeEvent.selection.start); - - this.setState( - { - valueBeforeCaret: newValueBeforeCaret, - caretContent: this.getNextChars(this.props.value, event.nativeEvent.selection.start), - }, - - () => { - const customEvent = { - nativeEvent: { - selection: { - start: event.nativeEvent.selection.start, - end: event.nativeEvent.selection.end, - positionX: this.textRef.current.offsetLeft - CONST.SPACE_CHARACTER_WIDTH, - positionY: this.textRef.current.offsetTop, - }, - }, - }; - this.props.onSelectionChange(customEvent); + const addCursorPositionToSelectionChange = (event) => { + if (shouldCalculateCaretPosition) { + // we do flushSync to make sure that the valueBeforeCaret is updated before we calculate the caret position to receive a proper position otherwise we will calculate position for the previous state + flushSync(() => { + setValueBeforeCaret(event.target.value.slice(0, event.nativeEvent.selection.start)); + setCaretContent(getNextChars(value, event.nativeEvent.selection.start)); + }); + const selectionValue = { + start: event.nativeEvent.selection.start, + end: event.nativeEvent.selection.end, + positionX: textRef.current.offsetLeft - CONST.SPACE_CHARACTER_WIDTH, + positionY: textRef.current.offsetTop, + }; + onSelectionChange({ + nativeEvent: { + selection: selectionValue, }, - ); - return; - } - - this.props.onSelectionChange(event); - } - - // Prevent onKeyPress from being triggered if the Enter key is pressed while text is being composed - handleKeyPress(e) { - if (!this.props.onKeyPress || isEnterWhileComposition(e)) { - return; + }); + setSelection(selectionValue); + } else { + onSelectionChange(event); + setSelection(event.nativeEvent.selection); } - this.props.onKeyPress(e); - } + }; /** * Set pasted text to clipboard * @param {String} text */ - paste(text) { + const paste = useCallback((text) => { try { - this.textInput.focus(); document.execCommand('insertText', false, text); - this.updateNumberOfLines(); - // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. - this.textInput.blur(); - this.textInput.focus(); + textInput.current.blur(); + textInput.current.focus(); // eslint-disable-next-line no-empty } catch (e) {} - } + }, []); /** * Manually place the pasted HTML into Composer * * @param {String} html - pasted HTML */ - handlePastedHTML(html) { - const parser = new ExpensiMark(); - this.paste(parser.htmlToMarkdown(html)); - } + const handlePastedHTML = useCallback( + (html) => { + const parser = new ExpensiMark(); + paste(parser.htmlToMarkdown(html)); + }, + [paste], + ); /** * Paste the plaintext content into Composer. * * @param {ClipboardEvent} event */ - handlePastePlainText(event) { - const plainText = event.clipboardData.getData('text/plain'); - this.paste(plainText); - } + const handlePastePlainText = useCallback( + (event) => { + const plainText = event.clipboardData.getData('text/plain'); + paste(plainText); + }, + [paste], + ); /** * Check the paste event for an attachment, parse the data and call onPasteFile from props with the selected file, @@ -305,164 +269,210 @@ class Composer extends React.Component { * * @param {ClipboardEvent} event */ - handlePaste(event) { - const isVisible = this.props.checkComposerVisibility(); - const isFocused = this.textInput.isFocused(); + const handlePaste = useCallback( + (event) => { + const isVisible = checkComposerVisibility(); + const isFocused = textInput.current.isFocused(); - if (!(isVisible || isFocused)) { - return; - } + if (!(isVisible || isFocused)) { + return; + } - if (this.textInput !== event.target) { - return; - } + if (textInput.current !== event.target) { + return; + } - event.preventDefault(); + event.preventDefault(); - const {files, types} = event.clipboardData; - const TEXT_HTML = 'text/html'; + const {files, types} = event.clipboardData; + const TEXT_HTML = 'text/html'; - // If paste contains files, then trigger file management - if (files.length > 0) { - // Prevent the default so we do not post the file name into the text box - this.props.onPasteFile(event.clipboardData.files[0]); - return; - } + // If paste contains files, then trigger file management + if (files.length > 0) { + // Prevent the default so we do not post the file name into the text box + onPasteFile(event.clipboardData.files[0]); + return; + } - // If paste contains HTML - if (types.includes(TEXT_HTML)) { - const pastedHTML = event.clipboardData.getData(TEXT_HTML); + // If paste contains HTML + if (types.includes(TEXT_HTML)) { + const pastedHTML = event.clipboardData.getData(TEXT_HTML); - const domparser = new DOMParser(); - const embeddedImages = domparser.parseFromString(pastedHTML, TEXT_HTML).images; + const domparser = new DOMParser(); + const embeddedImages = domparser.parseFromString(pastedHTML, TEXT_HTML).images; - // Exclude parsing img tags in the HTML, as fetching the image via fetch triggers a connect-src Content-Security-Policy error. - if (embeddedImages.length > 0 && embeddedImages[0].src) { - // If HTML has emoji, then treat this as plain text. - if (embeddedImages[0].dataset && embeddedImages[0].dataset.stringifyType === 'emoji') { - this.handlePastePlainText(event); - return; + // Exclude parsing img tags in the HTML, as fetching the image via fetch triggers a connect-src Content-Security-Policy error. + if (embeddedImages.length > 0 && embeddedImages[0].src) { + // If HTML has emoji, then treat this as plain text. + if (embeddedImages[0].dataset && embeddedImages[0].dataset.stringifyType === 'emoji') { + handlePastePlainText(event); + return; + } } + handlePastedHTML(pastedHTML); + return; } - - this.handlePastedHTML(pastedHTML); - return; - } - - this.handlePastePlainText(event); - } + handlePastePlainText(event); + }, + [onPasteFile, handlePastedHTML, checkComposerVisibility, handlePastePlainText], + ); /** * Manually scrolls the text input, then prevents the event from being passed up to the parent. * @param {Object} event native Event */ - handleWheel(event) { + const handleWheel = useCallback((event) => { if (event.target !== document.activeElement) { return; } - this.textInput.scrollTop += event.deltaY; + textInput.current.scrollTop += event.deltaY; event.preventDefault(); event.stopPropagation(); - } + }, []); /** - * We want to call updateNumberOfLines only when the parent doesn't provide value in props - * as updateNumberOfLines is already being called when value changes in componentDidUpdate + * Check the current scrollHeight of the textarea (minus any padding) and + * divide by line height to get the total number of rows for the textarea. */ - shouldCallUpdateNumberOfLines() { - if (!_.isEmpty(this.props.value)) { + const updateNumberOfLines = useCallback(() => { + if (textInput.current === null) { return; } + // we reset the height to 0 to get the correct scrollHeight + textInput.current.style.height = 0; + const computedStyle = window.getComputedStyle(textInput.current); + const lineHeight = parseInt(computedStyle.lineHeight, 10) || 20; + const paddingTopAndBottom = parseInt(computedStyle.paddingBottom, 10) + parseInt(computedStyle.paddingTop, 10); + setTextInputWidth(computedStyle.width); + + const computedNumberOfLines = ComposerUtils.getNumberOfLines(maxLines, lineHeight, paddingTopAndBottom, textInput.current.scrollHeight); + const generalNumberOfLines = computedNumberOfLines === 0 ? numberOfLinesProp : computedNumberOfLines; + + onNumberOfLinesChange(generalNumberOfLines); + updateIsFullComposerAvailable({isFullComposerAvailable, setIsFullComposerAvailable}, generalNumberOfLines); + setNumberOfLines(generalNumberOfLines); + textInput.current.style.height = 'auto'; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value, maxLines, numberOfLinesProp, onNumberOfLinesChange, isFullComposerAvailable, setIsFullComposerAvailable]); + + useEffect(() => { + updateNumberOfLines(); + }, [updateNumberOfLines]); + + useEffect(() => { + // we need to handle listeners on navigation focus/blur as Composer is not unmounting + // when navigating away to different report + const unsubscribeFocus = navigation.addListener('focus', () => document.addEventListener('paste', handlePaste)); + const unsubscribeBlur = navigation.addListener('blur', () => document.removeEventListener('paste', handlePaste)); + + if (_.isFunction(forwardedRef)) { + forwardedRef(textInput.current); + } - this.updateNumberOfLines(); - } - - /** - * Check the current scrollHeight of the textarea (minus any padding) and - * divide by line height to get the total number of rows for the textarea. - */ - updateNumberOfLines() { - // Hide the composer expand button so we can get an accurate reading of - // the height of the text input - this.props.setIsFullComposerAvailable(false); - - // We have to reset the rows back to the minimum before updating so that the scrollHeight is not - // affected by the previous row setting. If we don't, rows will be added but not removed on backspace/delete. - this.setState({numberOfLines: 1}, () => { - const computedStyle = window.getComputedStyle(this.textInput); - const lineHeight = parseInt(computedStyle.lineHeight, 10) || 20; - const paddingTopAndBottom = parseInt(computedStyle.paddingBottom, 10) + parseInt(computedStyle.paddingTop, 10); - const computedNumberOfLines = ComposerUtils.getNumberOfLines(this.props.maxLines, lineHeight, paddingTopAndBottom, this.textInput.scrollHeight); - const numberOfLines = computedNumberOfLines === 0 ? this.props.numberOfLines : computedNumberOfLines; - updateIsFullComposerAvailable(this.props, numberOfLines); - this.setState({ - numberOfLines, - width: computedStyle.width, - }); - this.props.onNumberOfLinesChange(numberOfLines); - }); - } + if (textInput.current) { + document.addEventListener('paste', handlePaste); + textInput.current.addEventListener('wheel', handleWheel); + } - render() { - const propStyles = StyleSheet.flatten(this.props.style); - propStyles.outline = 'none'; - const propsWithoutStyles = _.omit(this.props, 'style'); - - // This code creates a hidden text component that helps track the caret position in the visible input. - const renderElementForCaretPosition = ( - { + if (!isReportActionCompose) { + ReportActionComposeFocusManager.clear(); + } + unsubscribeFocus(); + unsubscribeBlur(); + document.removeEventListener('paste', handlePaste); + // eslint-disable-next-line es/no-optional-chaining + if (!textInput.current) { + return; + } + textInput.current.removeEventListener('wheel', handleWheel); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleKeyPress = useCallback( + (e) => { + // Prevent onKeyPress from being triggered if the Enter key is pressed while text is being composed + if (!onKeyPress || isEnterWhileComposition(e)) { + return; + } + onKeyPress(e); + }, + [onKeyPress], + ); + + const renderElementForCaretPosition = ( + + + {`${valueBeforeCaret} `} - {`${this.state.valueBeforeCaret} `} - - {`${this.state.caretContent}`} - + {`${caretContent}`} - - ); - - // We're disabling autoCorrect for iOS Safari until Safari fixes this issue. See https://github.com/Expensify/App/issues/8592 - return ( - <> - (this.textInput = el)} - selection={this.state.selection} - onChange={this.shouldCallUpdateNumberOfLines} - style={[ - propStyles, - - // We are hiding the scrollbar to prevent it from reducing the text input width, - // so we can get the correct scroll height while calculating the number of lines. - this.state.numberOfLines < this.props.maxLines ? styles.overflowHidden : {}, - StyleUtils.getComposeTextAreaPadding(this.props.numberOfLines), - ]} - /* eslint-disable-next-line react/jsx-props-no-spreading */ - {...propsWithoutStyles} - onSelectionChange={this.addCursorPositionToSelectionChange} - numberOfLines={this.state.numberOfLines} - disabled={this.props.isDisabled} - onKeyPress={this.handleKeyPress} - /> - {this.props.shouldCalculateCaretPosition && renderElementForCaretPosition} - - ); - } + + + ); + + const inputStyleMemo = useMemo( + () => [ + // We are hiding the scrollbar to prevent it from reducing the text input width, + // so we can get the correct scroll height while calculating the number of lines. + numberOfLines < maxLines ? styles.overflowHidden : {}, + + StyleSheet.flatten([style, {outline: 'none'}]), + StyleUtils.getComposeTextAreaPadding(numberOfLinesProp), + ], + [style, maxLines, numberOfLinesProp, numberOfLines], + ); + + return ( + <> + (textInput.current = el)} + selection={selection} + style={inputStyleMemo} + value={value} + forwardedRef={forwardedRef} + defaultValue={defaultValue} + autoFocus={autoFocus} + /* eslint-disable-next-line react/jsx-props-no-spreading */ + {...props} + onSelectionChange={addCursorPositionToSelectionChange} + numberOfLines={numberOfLines} + disabled={isDisabled} + onKeyPress={handleKeyPress} + onFocus={(e) => { + ReportActionComposeFocusManager.onComposerFocus(() => { + if (!textInput.current) { + return; + } + + textInput.current.focus(); + }); + if (props.onFocus) { + props.onFocus(e); + } + }} + /> + {shouldCalculateCaretPosition && renderElementForCaretPosition} + + ); } Composer.propTypes = propTypes; diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js index 6981fd451309..9a72d4e7d584 100644 --- a/src/components/ConfirmContent.js +++ b/src/components/ConfirmContent.js @@ -8,6 +8,8 @@ import Button from './Button'; import useLocalize from '../hooks/useLocalize'; import useNetwork from '../hooks/useNetwork'; import Text from './Text'; +import variables from '../styles/variables'; +import Icon from './Icon'; const propTypes = { /** Title of the modal */ @@ -40,9 +42,30 @@ const propTypes = { /** Whether we should show the cancel button */ shouldShowCancelButton: PropTypes.bool, + /** Icon to display above the title */ + iconSource: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + + /** Whether to center the icon / text content */ + shouldCenterContent: PropTypes.bool, + + /** Whether to stack the buttons */ + shouldStackButtons: PropTypes.bool, + + /** Styles for title */ + // eslint-disable-next-line react/forbid-prop-types + titleStyles: PropTypes.arrayOf(PropTypes.object), + + /** Styles for prompt */ + // eslint-disable-next-line react/forbid-prop-types + promptStyles: PropTypes.arrayOf(PropTypes.object), + /** Styles for view */ // eslint-disable-next-line react/forbid-prop-types contentStyles: PropTypes.arrayOf(PropTypes.object), + + /** Styles for icon */ + // eslint-disable-next-line react/forbid-prop-types + iconAdditionalStyles: PropTypes.arrayOf(PropTypes.object), }; const defaultProps = { @@ -55,36 +78,87 @@ const defaultProps = { shouldDisableConfirmButtonWhenOffline: false, shouldShowCancelButton: true, contentStyles: [], + iconSource: null, + shouldCenterContent: false, + shouldStackButtons: true, + titleStyles: [], + promptStyles: [], + iconAdditionalStyles: [], }; function ConfirmContent(props) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); + const isCentered = props.shouldCenterContent; + return ( - -
+ + {!_.isEmpty(props.iconSource) || + (_.isFunction(props.iconSource) && ( + + + + ))} + + +
+ + + {_.isString(props.prompt) ? {props.prompt} : props.prompt} - {_.isString(props.prompt) ? {props.prompt} : props.prompt} - -