diff --git a/.eslintignore b/.eslintignore
index d3e8a6328bc4..396bfd28c614 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
**/node_modules/*
**/dist/*
.github/actions/**/index.js"
+docs/vendor/**
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c106a25c8ab1..4f78ee6b69bf 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,5 +1,2 @@
# Every PR gets a review from an internal Expensify engineer
* @Expensify/pullerbear
-
-# Every PR that touches redirects gets reviewed by ring0
-docs/redirects.csv @Expensify/infra
\ No newline at end of file
diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml
index 52fb097d254e..94ea94d27505 100644
--- a/.github/actions/composite/setupGitForOSBotifyApp/action.yml
+++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml
@@ -33,7 +33,7 @@ runs:
fi
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
if: steps.key_check.outputs.key_exists != 'true'
with:
sparse-checkout: |
diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml
index 57c7e4d91379..7e1b5fbbae90 100644
--- a/.github/actions/composite/setupNode/action.yml
+++ b/.github/actions/composite/setupNode/action.yml
@@ -4,7 +4,7 @@ description: Set up Node
runs:
using: composite
steps:
- - uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516
+ - uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: npm
diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js
index abf0712103a5..b7ab57e68974 100644
--- a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js
+++ b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js
@@ -194,20 +194,38 @@ const {getPreviousVersion, SEMANTIC_VERSION_LEVELS} = __nccwpck_require__(8007);
*/
function fetchTag(tag) {
const previousPatchVersion = getPreviousVersion(tag, SEMANTIC_VERSION_LEVELS.PATCH);
- try {
- let command = `git fetch origin tag ${tag} --no-tags`;
+ let shouldRetry = true;
+ let needsRepack = false;
+ while (shouldRetry) {
+ try {
+ if (needsRepack) {
+ // We have seen some scenarios where this fixes the git fetch.
+ // Why? Who knows... https://github.com/Expensify/App/pull/31459
+ execSync('git repack -d');
+ }
- // Exclude commits reachable from the previous patch version (i.e: previous checklist),
- // so that we don't have to fetch the full history
- // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case
- if (previousPatchVersion !== tag) {
- command += ` --shallow-exclude=${previousPatchVersion}`;
- }
+ let command = `git fetch origin tag ${tag} --no-tags`;
- console.log(`Running command: ${command}`);
- execSync(command);
- } catch (e) {
- console.error(e);
+ // Exclude commits reachable from the previous patch version (i.e: previous checklist),
+ // so that we don't have to fetch the full history
+ // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case
+ if (previousPatchVersion !== tag) {
+ command += ` --shallow-exclude=${previousPatchVersion}`;
+ }
+
+ console.log(`Running command: ${command}`);
+ execSync(command);
+ shouldRetry = false;
+ } catch (e) {
+ console.error(e);
+ if (!needsRepack) {
+ console.log('Attempting to repack and retry...');
+ needsRepack = true;
+ } else {
+ console.error("Repack didn't help, giving up...");
+ shouldRetry = false;
+ }
+ }
}
}
diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js
index dbe109d99e32..1217c5e97de4 100644
--- a/.github/actions/javascript/getDeployPullRequestList/index.js
+++ b/.github/actions/javascript/getDeployPullRequestList/index.js
@@ -136,20 +136,38 @@ const {getPreviousVersion, SEMANTIC_VERSION_LEVELS} = __nccwpck_require__(8007);
*/
function fetchTag(tag) {
const previousPatchVersion = getPreviousVersion(tag, SEMANTIC_VERSION_LEVELS.PATCH);
- try {
- let command = `git fetch origin tag ${tag} --no-tags`;
+ let shouldRetry = true;
+ let needsRepack = false;
+ while (shouldRetry) {
+ try {
+ if (needsRepack) {
+ // We have seen some scenarios where this fixes the git fetch.
+ // Why? Who knows... https://github.com/Expensify/App/pull/31459
+ execSync('git repack -d');
+ }
- // Exclude commits reachable from the previous patch version (i.e: previous checklist),
- // so that we don't have to fetch the full history
- // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case
- if (previousPatchVersion !== tag) {
- command += ` --shallow-exclude=${previousPatchVersion}`;
- }
+ let command = `git fetch origin tag ${tag} --no-tags`;
- console.log(`Running command: ${command}`);
- execSync(command);
- } catch (e) {
- console.error(e);
+ // Exclude commits reachable from the previous patch version (i.e: previous checklist),
+ // so that we don't have to fetch the full history
+ // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case
+ if (previousPatchVersion !== tag) {
+ command += ` --shallow-exclude=${previousPatchVersion}`;
+ }
+
+ console.log(`Running command: ${command}`);
+ execSync(command);
+ shouldRetry = false;
+ } catch (e) {
+ console.error(e);
+ if (!needsRepack) {
+ console.log('Attempting to repack and retry...');
+ needsRepack = true;
+ } else {
+ console.error("Repack didn't help, giving up...");
+ shouldRetry = false;
+ }
+ }
}
}
diff --git a/.github/libs/GitUtils.js b/.github/libs/GitUtils.js
index 7bc600470dd1..42a7ecff1263 100644
--- a/.github/libs/GitUtils.js
+++ b/.github/libs/GitUtils.js
@@ -9,20 +9,38 @@ const {getPreviousVersion, SEMANTIC_VERSION_LEVELS} = require('../libs/versionUp
*/
function fetchTag(tag) {
const previousPatchVersion = getPreviousVersion(tag, SEMANTIC_VERSION_LEVELS.PATCH);
- try {
- let command = `git fetch origin tag ${tag} --no-tags`;
-
- // Exclude commits reachable from the previous patch version (i.e: previous checklist),
- // so that we don't have to fetch the full history
- // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case
- if (previousPatchVersion !== tag) {
- command += ` --shallow-exclude=${previousPatchVersion}`;
- }
+ let shouldRetry = true;
+ let needsRepack = false;
+ while (shouldRetry) {
+ try {
+ if (needsRepack) {
+ // We have seen some scenarios where this fixes the git fetch.
+ // Why? Who knows... https://github.com/Expensify/App/pull/31459
+ execSync('git repack -d');
+ }
- console.log(`Running command: ${command}`);
- execSync(command);
- } catch (e) {
- console.error(e);
+ let command = `git fetch origin tag ${tag} --no-tags`;
+
+ // Exclude commits reachable from the previous patch version (i.e: previous checklist),
+ // so that we don't have to fetch the full history
+ // Note that this condition would only ever _not_ be true in the 1.0.0-0 edge case
+ if (previousPatchVersion !== tag) {
+ command += ` --shallow-exclude=${previousPatchVersion}`;
+ }
+
+ console.log(`Running command: ${command}`);
+ execSync(command);
+ shouldRetry = false;
+ } catch (e) {
+ console.error(e);
+ if (!needsRepack) {
+ console.log('Attempting to repack and retry...');
+ needsRepack = true;
+ } else {
+ console.error("Repack didn't help, giving up...");
+ shouldRetry = false;
+ }
+ }
}
}
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index 68b98ab625be..c904a459d1c0 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -42,12 +42,12 @@ Due to the large, ever-growing history of this repo, do not do any full-fetches
```yaml
# Bad
-- uses: actions/checkout@v3
+- uses: actions/checkout@v4
with:
fetch-depth: 0
# Good
-- uses: actions/checkout@v3
+- uses: actions/checkout@v4
```
```sh
diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml
index 43f3c64554bc..92480a94ba53 100644
--- a/.github/workflows/cherryPick.yml
+++ b/.github/workflows/cherryPick.yml
@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout staging branch
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: staging
token: ${{ secrets.OS_BOTIFY_TOKEN }}
diff --git a/.github/workflows/createNewVersion.yml b/.github/workflows/createNewVersion.yml
index c9c97d5355fb..812ec200bd88 100644
--- a/.github/workflows/createNewVersion.yml
+++ b/.github/workflows/createNewVersion.yml
@@ -68,7 +68,7 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
- name: Check out
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: main
# The OS_BOTIFY_COMMIT_TOKEN is a personal access token tied to osbotify
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 78040f237689..4aa1a6a27d1a 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/production'
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
name: Checkout
with:
ref: production
diff --git a/.github/workflows/deployBlocker.yml b/.github/workflows/deployBlocker.yml
index f42d19ca8241..6356d7f65a4d 100644
--- a/.github/workflows/deployBlocker.yml
+++ b/.github/workflows/deployBlocker.yml
@@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml
index 9d94bf900615..318198981097 100644
--- a/.github/workflows/e2ePerformanceTests.yml
+++ b/.github/workflows/e2ePerformanceTests.yml
@@ -22,7 +22,7 @@ jobs:
outputs:
VERSION: ${{ steps.getMostRecentRelease.outputs.VERSION }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Get most recent release version
id: getMostRecentRelease
@@ -78,7 +78,7 @@ jobs:
outputs:
DELTA_REF: ${{ steps.getDeltaRef.outputs.DELTA_REF }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Get pull request details
id: getPullRequestDetails
@@ -154,7 +154,7 @@ jobs:
needs: [buildBaseline, buildDelta]
name: Run E2E tests in AWS device farm
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
diff --git a/.github/workflows/finishReleaseCycle.yml b/.github/workflows/finishReleaseCycle.yml
index f8b68786aaab..6c3f3dfd7603 100644
--- a/.github/workflows/finishReleaseCycle.yml
+++ b/.github/workflows/finishReleaseCycle.yml
@@ -13,7 +13,7 @@ jobs:
isValid: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) && !fromJSON(steps.checkDeployBlockers.outputs.HAS_DEPLOY_BLOCKERS) }}
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.OS_BOTIFY_TOKEN }}
@@ -77,7 +77,7 @@ jobs:
if: ${{ fromJSON(needs.validate.outputs.isValid) }}
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: staging
token: ${{ secrets.OS_BOTIFY_TOKEN }}
@@ -119,7 +119,7 @@ jobs:
needs: [updateProduction, createNewPatchVersion]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.OS_BOTIFY_TOKEN }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 3072b3354a84..22a60992e7c7 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
diff --git a/.github/workflows/lockDeploys.yml b/.github/workflows/lockDeploys.yml
index 6ca025bb2a25..6a2812a4f92a 100644
--- a/.github/workflows/lockDeploys.yml
+++ b/.github/workflows/lockDeploys.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: macos-12
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.OS_BOTIFY_TOKEN }}
diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml
index d494ea0d008b..19c5cf9c90ef 100644
--- a/.github/workflows/platformDeploy.yml
+++ b/.github/workflows/platformDeploy.yml
@@ -41,7 +41,7 @@ jobs:
needs: validateActor
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
@@ -62,7 +62,7 @@ jobs:
runs-on: ubuntu-latest-xl
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Configure MapBox SDK
run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
@@ -146,7 +146,7 @@ jobs:
runs-on: macos-12-xl
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
@@ -185,7 +185,7 @@ jobs:
runs-on: macos-13-xlarge
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Configure MapBox SDK
run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
@@ -297,7 +297,7 @@ jobs:
runs-on: ubuntu-latest-xl
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
@@ -367,7 +367,7 @@ jobs:
needs: [android, desktop, iOS, web]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set version
run: echo "VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV"
@@ -428,7 +428,7 @@ jobs:
needs: [android, desktop, iOS, web]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml
index bae843e74709..54fd1a830b8b 100644
--- a/.github/workflows/preDeploy.yml
+++ b/.github/workflows/preDeploy.yml
@@ -86,7 +86,7 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
- name: Checkout main
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.OS_BOTIFY_TOKEN }}
diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml
index b259ff9052b6..4aaa6fb2ce8c 100644
--- a/.github/workflows/reassurePerformanceTests.yml
+++ b/.github/workflows/reassurePerformanceTests.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup NodeJS
uses: Expensify/App/.github/actions/composite/setupNode@main
diff --git a/.github/workflows/shellCheck.yml b/.github/workflows/shellCheck.yml
index 609541e9a660..366caa8a0d19 100644
--- a/.github/workflows/shellCheck.yml
+++ b/.github/workflows/shellCheck.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Lint shell scripts with ShellCheck
run: npm run shellcheck
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fa47a2f61d4a..9c2e9486150b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -20,7 +20,7 @@ jobs:
name: test (job ${{ fromJSON(matrix.chunk) }})
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
@@ -44,7 +44,7 @@ jobs:
runs-on: ubuntu-latest
name: Storybook tests
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: Expensify/App/.github/actions/composite/setupNode@main
@@ -57,7 +57,7 @@ jobs:
name: Shell tests
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml
index b79b687e638e..1f266c59d0d1 100644
--- a/.github/workflows/testBuild.yml
+++ b/.github/workflows/testBuild.yml
@@ -221,7 +221,7 @@ jobs:
runs-on: macos-12-xl
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }}
@@ -264,7 +264,7 @@ jobs:
runs-on: ubuntu-latest-xl
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }}
diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml
index 3e54975433f6..c09db594e243 100644
--- a/.github/workflows/typecheck.yml
+++ b/.github/workflows/typecheck.yml
@@ -5,14 +5,14 @@ on:
pull_request:
types: [opened, synchronize]
branches-ignore: [staging, production]
- paths: ['**.ts', '**.tsx', 'package.json', 'package-lock.json', 'tsconfig.json']
+ paths: ['**.js', '**.ts', '**.tsx', 'package.json', 'package-lock.json', 'tsconfig.json']
jobs:
typecheck:
if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: Expensify/App/.github/actions/composite/setupNode@main
@@ -20,3 +20,12 @@ jobs:
run: npm run typecheck
env:
CI: true
+
+ - name: Check for new JavaScript files
+ run: |
+ git fetch origin main --no-tags --depth=1
+ count_new_js=$(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/libs/*.js' 'src/hooks/*.js' 'src/styles/*.js' 'src/languages/*.js' | wc -l)
+ if [ "$count_new_js" -gt "0" ]; then
+ echo "ERROR: Found new JavaScript files in the /src/libs, /src/hooks, /src/styles, or /src/languages directories; use TypeScript instead."
+ exit 1
+ fi
diff --git a/.github/workflows/validateDocsRoutes.yml b/.github/workflows/validateDocsRoutes.yml
index 14c08e087565..702c48fbc068 100644
--- a/.github/workflows/validateDocsRoutes.yml
+++ b/.github/workflows/validateDocsRoutes.yml
@@ -11,7 +11,7 @@ jobs:
if: github.actor != 'OSBotify' && github.actor != 'imgbot[bot]'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: Expensify/App/.github/actions/composite/setupNode@main
diff --git a/.github/workflows/validateGithubActions.yml b/.github/workflows/validateGithubActions.yml
index 5cfc4670620f..c493e26bc514 100644
--- a/.github/workflows/validateGithubActions.yml
+++ b/.github/workflows/validateGithubActions.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
diff --git a/.github/workflows/verifyPodfile.yml b/.github/workflows/verifyPodfile.yml
index d98780e3e829..08f9c3a5223b 100644
--- a/.github/workflows/verifyPodfile.yml
+++ b/.github/workflows/verifyPodfile.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Node
uses: Expensify/App/.github/actions/composite/setupNode@main
- name: Verify podfile
diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml
index 43e0e1650381..1ea81129fc15 100644
--- a/.github/workflows/welcome.yml
+++ b/.github/workflows/welcome.yml
@@ -10,7 +10,7 @@ jobs:
if: ${{ github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' }}
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Get merged pull request
id: getMergedPullRequest
diff --git a/.nvmrc b/.nvmrc
index d9289897d305..b8c9fdcbe36b 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-16.15.1
+16.15.1
\ No newline at end of file
diff --git a/__mocks__/fs.js b/__mocks__/fs.js
new file mode 100644
index 000000000000..cca0aa9520ec
--- /dev/null
+++ b/__mocks__/fs.js
@@ -0,0 +1,3 @@
+const {fs} = require('memfs');
+
+module.exports = fs;
diff --git a/__mocks__/fs/promises.js b/__mocks__/fs/promises.js
new file mode 100644
index 000000000000..1a58f0f013ac
--- /dev/null
+++ b/__mocks__/fs/promises.js
@@ -0,0 +1,3 @@
+const {fs} = require('memfs');
+
+module.exports = fs.promises;
diff --git a/android/app/build.gradle b/android/app/build.gradle
index af7f43adad0d..274a0d55eb37 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -91,8 +91,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001039900
- versionName "1.3.99-0"
+ versionCode 1001040003
+ versionName "1.4.0-3"
}
flavorDimensions "default"
diff --git a/assets/images/empty-state__attach-receipt.svg b/assets/images/empty-state__attach-receipt.svg
new file mode 100644
index 000000000000..6b50afbdbf0b
--- /dev/null
+++ b/assets/images/empty-state__attach-receipt.svg
@@ -0,0 +1,16 @@
+
diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md
index 112c3b9617c9..f2ff837d7638 100644
--- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md
+++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md
@@ -1,5 +1,91 @@
---
-title: Connect Company Cards
-description: Connect Company Cards
+title: Company-Card-Settings.md
+description: Company card settings
---
-## Resource Coming Soon!
+# Overview
+Once you’ve imported your company cards via commercial card feed, direct bank feed, or CSV import, the next step is to configure the cards’ settings.
+
+As a Domain Admin, you can access the company card settings by navigating to Settings > Domains> Domain Name > Company Card > Settings.
+
+If you cannot access Domains, you will need to request Domain Admin access my the Domain Admin.
+
+# How to configure company card settings
+You can manage company cards and set and adjust the settings from the Domains page by navigating to Settings > Domains > [Domain name] > Settings
+
+## Reimbursable preference
+
+You can control how your employees' company card expenses are flagged for reimbursement:
+
+Force Yes: All expenses will be marked as reimbursable, and employees cannot change this setting.
+Force No: All expenses will be marked as non-reimbursable, and employees cannot change this setting.
+Do Not Force: Expenses will default to either reimbursable or non-reimbursable (your choice), but employees can adjust if necessary.
+
+## Liability type
+
+Choose the liability type that suits your needs:
+
+Corporate Liability: Users cannot delete company card expenses.
+Personal Liability: Users are allowed to delete company card expenses.
+
+If you update the settings on an existing company card feed, the changes will apply to expenses imported after the date the setting is saved. The update will not affect previously imported expenses.
+
+## Preferred policy
+
+Setting a preferred policy for a company card feed will ensure that the imported transactions are added to a report on the policy you set. This setting is useful when members are on multiple policies and need to ensure their company card expenses are reported to a particular policy.
+
+# How to use Scheduled Submit with company cards
+All expenses must be placed on a report if they need to be approved; with Scheduled Submit, you no longer need to worry about the arduous task of employees creating their expenses, adding them to a report, and submitting them manually. All they need to do is SmartScan their receipts and Concierge will take care of the rest, on a variety of schedules that you can set according to your preferences!
+
+Concierge won't automatically submit expenses on reports that have Expense Violations. Instead, these expenses will be moved to a new report, creating an additional report for the current reporting period.
+
+An employee can add comments in the Expense Comment field or at the bottom of the report to clarify any details.
+
+## Enable Scheduled Submit
+Scheduled Submit is enabled in the Group Policy by navigating to Settings > Policies > Group > Policy Name > Reports > Scheduled Submit
+Use the toggle to enable Scheduled Submit
+Choose your desired frequency
+
+If Scheduled Submit is disabled on the group policy level (or set to a manual frequency), and you have noticed expense reports are still automatically submitted to the group policy, it's likely Scheduled Submit is enabled on the user’s Individual Policy settings.
+
+# How to connect company cards to an accounting integration
+
+If you're using a connected accounting system such as NetSuite, Xero, Intacct, Quickbooks Desktop, or QuickBooks Online, you can also connect the card to export to a specific credit card GL account. First, connect the card itself, and once completed, follow the steps below:
+Go to Settings > Domains > Domain name > Company Cards
+Click Edit Exports on the right-hand side of the card table and select the GL account you want to export expenses to
+You're all done. After the account is set, exported expenses will be mapped to the specific account selected when exported by a Domain Admin.
+
+# How to export company card expenses to a connected accounting integration
+
+## Pooled GL account
+
+To export credit card expenses to a pooled GL account:
+Go to Settings > Policies > Group > Policy Name > Connections > Accounting Integrations > Configure
+Select Credit Card / Charge Card / Bank Transaction as your Non-reimbursable export option.
+Please review the Export Settings page for exporting Expense Reports to NetSuite
+Select the Vendor/liability account you want to export all non-reimbursable expenses to.
+
+## Individual GL account
+
+Go to Settings > Domain > Domain name > Company Cards
+Click the Export Settings cog to the right-hand side of the card and select the GL account you want to export expenses to.
+You're all done! After the account is set, exported expenses will be mapped to the specific account selected.
+
+# Deep Dive
+## Identifying company card transactions
+When you link your credit cards to Expensify, the transactions will appear in each user's account on the Expenses page as soon as they're posted. You can identify transactions from centrally managed cards by seeing the locked card icon next to them. That icon indicates that they’re company card expenses:
+[add image here]
+
+## Importing historical transactions
+
+After a card is connected via direct connection or via Approved! banks, Expensify will import 30-90 days' worth of historical transactions to your account (the timeframe is based on your bank's discretion). Any historical expenses beyond that date range can be imported using the CSV spreadsheet import method.
+
+## Using eReceipts
+Expensify eReceipts serve as digital substitutes for paper receipts in your purchase transactions, eliminating the necessity to retain physical receipts or utilize SmartScanning receipts. In the case of Expensify Card transactions, eReceipts are automatically generated for all amounts. For other card programs, eReceipts are specifically generated for purchases amounting to $75 or less, provided the transactions are in USD.
+To ensure seamless automatic importation, it's essential to maintain your transactions in US Dollars. Additionally, eReceipts can be directly imported from your bank account. Please be aware that CSV/OFX imported files of bank transactions do not support eReceipts.
+It's important to note that eReceipts are not generated for lodging expenses. Moreover, due to incomplete or inaccurate category information from certain banks, there may be instances of invalid eReceipts being generated for hotel purchases. If you choose to re-categorize expenses, a similar situation may arise. It's crucial to remember that our Expensify eReceipt Guarantee excludes coverage for hotel and motel expenses.
+
+# FAQ
+## What plan/subscription is required in order to manage corporate cards?
+Group Policy (Collect or Control plan only)
+## When do my company card transactions import to Expensify?
+Credit card transactions are imported to Expensify once they’re posted to the bank account. This usually takes 1-3 business days between the point of purchase and when the transactions populate in your account.
diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md b/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md
index e08aaa3d6094..4f660588d432 100644
--- a/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md
+++ b/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md
@@ -1,5 +1,44 @@
---
title: Free Trial
-description: Free Trial
+description: Learn more about your free trial with Expensify.
---
-## Resource Coming Soon!
+
+# Overview
+New customers can take advantage of a seven-day Free Trial on a group Workspace. This trial period allows you to fully explore Expensify's features and capabilities before deciding on a subscription.
+During the trial, your organization will have complete access to all the features and functionality offered by the Collect or Control workspace plan. This post provides a step-by-step guide on how to begin, oversee, and successfully conclude your organization's Expensify Free Trial.
+
+# How to start a Free Trial
+1. Sign up for a new Expensify account at expensify.com.
+2. After you've signed up for a new Expensify account, you will see a task on your Home page asking if you are using Expensify for your business or as an individual.
+ a. **Note**: If you select “Individual”, Expensify is free for individuals for up to 25 SmartScans per month. Selecting Individual will **not** start a Free Trial. More details on individual subscriptions can be found [here](https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription).
+3. Select the Business option.
+4. Select which Expensify features you'd like to set up for your organization.
+5. Congratulations, your seven-day Free Trial has started!
+
+Once you've made these selections, we'll automatically enroll you in a Free Trial and create a Group Workspace, which will trigger new tasks on your Home page to walk you through how to configure Expensify for your organization. If you have any questions about a specific task or need assistance setting up your company, you can speak with your designated Setup Specialist by clicking “Support” on the left-hand navigation menu and selecting their name. This will allow you to message your Setup Specialist, and request a call if you need.
+
+# How to unlock additional Free Trial weeks
+When you begin a Free Trial, you'll have an initial seven-day period before you need to provide your billing information to continue using Expensify. Luckily, Expensify offers the option to extend your Free Trial by an additional five weeks!
+
+To access these extra free weeks, all you need to do is complete the tasks on your Home page marked with the "Free Week!" banner. Each task completed in this category will automatically add seven more days to your trial. You can easily keep track of the remaining days of your Free Trial by checking the top right-hand corner of your Expensify Home page.
+
+# How to make the most of your Free Trial
+- Complete all of the "Free Week!" tasks right away. These tasks are crucial for establishing your organization's Workspace, and finishing them will give you a clear idea of how much time you have left in your Free Trial.
+
+- Every Free Trial has dedicated access to a Setup Specialist who can help you set up your account to your preferences. We highly recommend booking a call with your dedicated Setup Specialist as soon as you start your Free Trial. If you ever need assistance with a setup task, your tasks also include demo videos.
+
+- Invite a few employees to join Expensify as early as possible during the Free Trial. Bringing employees on board and having them submit expenses will allow you to fully experience how all of the features and functionalities of Expensify work together to save time. We provide excellent resources to help employees get started with Expensify.
+
+- Establish a connection between Expensify and your accounting system from the outset. By doing this early, you can start testing Expensify comprehensively from end to end.
+
+# FAQ
+## What happens when my Free Trial ends?
+If you’ve already added a billing card to Expensify, you will automatically start your organization’s Expensify subscription after your Free Trial ends. At the beginning of the following month, we'll bill the card you have on file for your subscription, adjusting the charge to exclude the Free Trial period.
+If your Free Trial concludes without a billing card on file, you will see a notification on your Home page saying, 'Your Free Trial has expired.'
+If you still have outstanding 'Free Week!' tasks, completing them will extend your Free Trial by additional days.
+If you continue without adding a billing card, you will be granted a five-day grace period after the following billing cycle before all Group Workspace functionality is disabled. To continue using Expensify's Group Workspace features, you will need to input your billing card information and initiate a subscription.
+
+## How can I downgrade my account after my Free Trial ends?
+If you’d like to downgrade to an individual account after your Free Trial has ended, you will need to delete any Group Workspace that you have created. This action will remove the Workspaces, subscription, and any amount owed. You can do this in one of two ways from the Expensify web app:
+- Select the “Downgrade” option on the billing card task on your Home page.
+- Go to **Settings > Workspaces > [Workspace name]**, then click the gear button next to the Workspace and select Delete.
diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md
index 42a8a914e5bc..5431355dd790 100644
--- a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md
+++ b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md
@@ -66,7 +66,7 @@ A Workspace admin can see Processing, Approved, and Reimbursed expenses as long
If employees submit expense reports on a workspace where you are not an admin, you will not have visibility into those expenses. Additionally, if an expense is left unreported, a workspace admin will not be able to see that expense until it’s been added to a report.
A Workspace admin can edit the tags and categories on an expense, but if they want to edit the amount, date, or merchant name, the expense will need to be in a Processing state or rejected back to the submitter for changes.
-We have more about company card expense reconciliation in this support article.
+We have more about company card expense reconciliation in this [support article](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation).
## Can I edit multiple expenses at once?
Yes! Select the expenses you want to edit and click **Edit Multiple**.
diff --git a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md
deleted file mode 100644
index bdccbe927769..000000000000
--- a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-title: Welcome to the Expensify Lounge!
-description: How to get the most out of the Expensify Lounge.
-redirect_from: articles/other/Expensify-Lounge/
----
-
-
-# What is the Expensify Lounge?
-The Expensify Lounge is a place where people go to Get Shit Done. It's a beautiful environment with great coffee and a group of people to collaborate with. Check out this guide on how to best utilize the Expensify Lounge!
-
-# The Two Rules
-### Rule #1 - Get Shit Done
-
-The Lounge is a space for people to get work done. It is optimized to be the perfect environment for you to focus on your work, collaborate with others, and advance your most wild and creative ideas. To make this a reality, we ask our members to keep the following in mind:
-
-- **#focus** - Use the space for how it was designed and do not distract from others' focus. The space is beautiful, social, and collaborative, but it was created to help our members work effectively.
-- **#urgency** - Working remotely is great, but there's nothing like real-time collaboration with your colleagues. Use the lounge to meet with co-workers IRL to continue the progress on whatever it is you're working on.
-- **#results** - Don't mistake time for effort or effort for output. Upon arrival, visualize what you want to accomplish, and don't leave until it's done.
-
-## Rule #2 - Don’t Ruin it for Everyone Else
-
-We want this place to be incredible, innovative, and always elvoving. To achieve that, we have some general guidelines:
-
-- **#writeitdown** - If you can help others learn from you, do so. Write a blog post, a document, or a post in Expensify Chat to share with others. This includes making the Expensify Lounge a better space. Feel free to write down any improvements so we can make it better.
-- **#showup** - If you are in the lounge, be fully present. Meet others, and collaborate in social rooms. The point is to build a community of people who are focused on getting shit done; you’ll get out what you put in.
-- **#oneteam** - Providing an inclusive community is our priority, and we do not tolerate any form of discrimination. Aim to go out of your way to include people who want to be included.
-- **#nocreeps** - Do not make people feel uncomfortable with your words or actions. If you are made to feel uncomfortable or notice this happening to someone else, you can use the escalation process outlined in the FAQ section.
-
-# How to Use the Expensify Lounge
-Keeping those two rules in mind, below is a guide on how our members can get the most out of the lounge.
-
-### Rule #1 - Getting Shit Done
-- **Order drinks from Concierge** - [Write Concierge here](https://new.expensify.com/concierge) to ask lounge questions or order beverages. Concierge will bring your order directly to you!
-- **Using an office** - Offices are first come, first serve. If an office is open, feel free to use it! Please keep office use to under an hour. We currently do not allow reserving offices.
-- **Lounge hours** - The lounge will be open from 8am-6pm PT, Monday through Friday and closed on some major holidays. You can review our Google Maps profile to check our holiday hours.
-- **Make the lounge better** - Make any suggestions to improve the lounge experience in [#announce - Expensify Lounge](https://new.expensify.com/r/8292963527436014).
-
-## Rule #2 - Not Ruining it for Everyone Else
-- **Offices are for calls** - Please do not occupy an office unless you have a call or collaborative meeting happening, and don't stay in an office for longer than an hour.
-- **Respect other people** - Please do not be too loud or distracting while others are trying to work. While collaborating in Expensify Chat, be respectful of others’ viewpoints and keep a positive environment.
-- **Stay home if you’re sick** - If you feel sick, please do not visit the lounge, or consider wearing a mask in public areas.
-- **If you see something, say something** - If you are made to feel uncomfortable or witness others being made uncomfortable, let Concierge know. If this is happening in Expensify Chat, use our moderation tools (outlined below in the FAQ) to apply the applicable level of moderation.
-
-We’re so happy you are here to live rich, have fun, and save the world with us. Now, go enjoy the Expensify Lounge, and let's Get Shit Done!
-
-# FAQs
-
-#### What is Concierge?
-
-Concierge is our automated system that answers member questions in real-time. Questions regarding the local lounge will be routed directly to the lounge's Concierge. You can send Concierge a message if you have a drink request or general questions. They’ll take care of everything for you!
-
-#### Who is invited to the Expensify Lounge?
-
-Everyone is invited to the Expensify Lounge! Whether you're an existing customer, or you're someone looking for a great space to Get Shit Done, we'd love to have you.
-
-#### How do I escalate something that's making me or someone else uncomfortable?
-
-If you see something in Expensify Chat that should be escalated, you can use the escalation feature to mark a chat as:
-- **Spam or Inconsiderate**: This will send a whisper to the sender of the message warning them of the violation, and the message will have a flag applied to it which will be visible to all users. Concierge will not review these flags.
-- **Intimidating or Bullying**: The message will be immediately hidden, and the content will be reviewed by our team. After reviewing the message, and it's confirmed intimidation or bullying, the message will be permanently hidden and we'll communicate the violation to the sender of the message.
-- **Harassment or Assault**: The message will be immediately hidden and reviewed by our team. The user will be sent a message to warning them of the violation, and Concierge can block the user if that's deemed necessary.
-
-If you witness something in-person, please write to Concierge referencing which lounge you are in, and they will escalate the issue appropriately.
-
-#### Where are other Expensify Lounge locations?
-
-Right now, we only have the San Francisco Lounge, but be on the lookout for more coming soon!
diff --git a/docs/redirects.csv b/docs/redirects.csv
index 3bcfb594bedb..82add37c330c 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -17,4 +17,10 @@ https://community.expensify.com/discussion/5802/deep-dive-understanding-math-and
https://community.expensify.com/discussion/5796/deep-dive-user-level-formula,https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates
https://community.expensify.com/discussion/4750/how-to-create-a-custom-export,https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates
https://community.expensify.com/discussion/4642/how-to-export-reports-to-a-custom-template,https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates
-
+https://community.expensify.com/discussion/5648/deep-dive-policy-users-and-roles,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles
+https://community.expensify.com/discussion/5740/deep-dive-what-expense-information-is-available-based-on-role,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles
+https://community.expensify.com/discussion/4472/how-to-set-or-edit-a-user-role,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles
+https://community.expensify.com/discussion/5655/deep-dive-what-is-a-vacation-delegate,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate
+https://community.expensify.com/discussion/5194/how-to-assign-a-vacation-delegate-for-an-employee-through-domains,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate
+https://community.expensify.com/discussion/5190/how-to-individually-assign-a-vacation-delegate-from-account-settings,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate
+https://community.expensify.com/discussion/5274/how-to-set-up-an-adp-indirect-integration-with-expensify,https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/ADP
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index d40d36701731..9f7e4a190690 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.3.99
+ 1.4.0
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.3.99.0
+ 1.4.0.3
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 4e3ca3ebce6d..c5801e5606a8 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.3.99
+ 1.4.0
CFBundleSignature
????
CFBundleVersion
- 1.3.99.0
+ 1.4.0.3
diff --git a/package-lock.json b/package-lock.json
index 47f1ce14b2d9..ab5d586e12e8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.3.99-0",
+ "version": "1.4.0-3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.3.99-0",
+ "version": "1.4.0-3",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -92,7 +92,7 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.115",
+ "react-native-onyx": "1.0.118",
"react-native-pager-view": "^6.2.0",
"react-native-pdf": "^6.7.1",
"react-native-performance": "^5.1.0",
@@ -165,7 +165,6 @@
"@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",
"@types/react-beautiful-dnd": "^13.1.4",
@@ -213,8 +212,8 @@
"jest-cli": "29.4.1",
"jest-environment-jsdom": "^29.4.1",
"jest-transformer-svg": "^2.0.1",
+ "memfs": "^4.6.0",
"metro-react-native-babel-preset": "0.76.8",
- "mock-fs": "^4.13.0",
"onchange": "^7.1.0",
"portfinder": "^1.0.28",
"prettier": "^2.8.8",
@@ -241,8 +240,8 @@
"yaml": "^2.2.1"
},
"engines": {
- "node": "16.15.1",
- "npm": "8.11.0"
+ "node": ">=16.15.1 <=20.9.0",
+ "npm": ">=8.11.0 <=10.1.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -12823,6 +12822,18 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/@storybook/builder-webpack5/node_modules/memfs": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
+ "dev": true,
+ "dependencies": {
+ "fs-monkey": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/@storybook/builder-webpack5/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -16801,6 +16812,18 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/@storybook/manager-webpack5/node_modules/memfs": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
+ "dev": true,
+ "dependencies": {
+ "fs-monkey": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/@storybook/manager-webpack5/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -19413,15 +19436,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/mock-fs": {
- "version": "4.13.1",
- "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz",
- "integrity": "sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==",
- "dev": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/ms": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
@@ -30232,9 +30246,10 @@
"license": "MIT"
},
"node_modules/fast-diff": {
- "version": "1.2.0",
- "dev": true,
- "license": "Apache-2.0"
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true
},
"node_modules/fast-equals": {
"version": "4.0.3",
@@ -30976,6 +30991,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fork-ts-checker-webpack-plugin/node_modules/memfs": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
+ "dev": true,
+ "dependencies": {
+ "fs-monkey": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
@@ -31121,9 +31148,10 @@
}
},
"node_modules/fs-monkey": {
- "version": "1.0.3",
- "dev": true,
- "license": "Unlicense"
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz",
+ "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==",
+ "dev": true
},
"node_modules/fs-write-stream-atomic": {
"version": "1.0.10",
@@ -32759,6 +32787,15 @@
"which": "bin/which"
}
},
+ "node_modules/hyperdyperid": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
+ "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.18"
+ }
+ },
"node_modules/hyphenate-style-name": {
"version": "1.0.4",
"license": "BSD-3-Clause"
@@ -37359,6 +37396,13 @@
"integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==",
"license": "MIT"
},
+ "node_modules/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
+ "dev": true,
+ "peer": true
+ },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -38587,14 +38631,70 @@
}
},
"node_modules/memfs": {
- "version": "3.4.7",
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.6.0.tgz",
+ "integrity": "sha512-I6mhA1//KEZfKRQT9LujyW6lRbX7RkC24xKododIDO3AGShcaFAMKElv1yFGWX8fD4UaSiwasr3NeQ5TdtHY1A==",
"dev": true,
- "license": "Unlicense",
"dependencies": {
- "fs-monkey": "^1.0.3"
+ "json-joy": "^9.2.0",
+ "thingies": "^1.11.1"
},
"engines": {
"node": ">= 4.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true
+ },
+ "node_modules/memfs/node_modules/json-joy": {
+ "version": "9.9.1",
+ "resolved": "https://registry.npmjs.org/json-joy/-/json-joy-9.9.1.tgz",
+ "integrity": "sha512-/d7th2nbQRBQ/nqTkBe6KjjvDciSwn9UICmndwk3Ed/Bk9AqkTRm4PnLVfXG4DKbT0rEY0nKnwE7NqZlqKE6kg==",
+ "dev": true,
+ "dependencies": {
+ "arg": "^5.0.2",
+ "hyperdyperid": "^1.2.0"
+ },
+ "bin": {
+ "jj": "bin/jj.js",
+ "json-pack": "bin/json-pack.js",
+ "json-pack-test": "bin/json-pack-test.js",
+ "json-patch": "bin/json-patch.js",
+ "json-patch-test": "bin/json-patch-test.js",
+ "json-pointer": "bin/json-pointer.js",
+ "json-pointer-test": "bin/json-pointer-test.js",
+ "json-unpack": "bin/json-unpack.js"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "quill-delta": "^5",
+ "rxjs": "7",
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "tslib": "^2.1.0"
}
},
"node_modules/memoize-one": {
@@ -40887,13 +40987,6 @@
"node": ">=10"
}
},
- "node_modules/mock-fs": {
- "version": "4.14.0",
- "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz",
- "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -43651,6 +43744,21 @@
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
},
+ "node_modules/quill-delta": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
+ "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "fast-diff": "^1.3.0",
+ "lodash.clonedeep": "^4.5.0",
+ "lodash.isequal": "^4.5.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
"node_modules/raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
@@ -44323,17 +44431,17 @@
}
},
"node_modules/react-native-onyx": {
- "version": "1.0.115",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.115.tgz",
- "integrity": "sha512-uPrJcw3Ta/EFL3Mh3iUggZ7EeEwLTSSSc5iUkKAA+a9Y8kBo8+6MWup9VCM/4wgysZbf3VHUGJCWQ8H3vWKgUg==",
+ "version": "1.0.118",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz",
+ "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==",
"dependencies": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
"underscore": "^1.13.6"
},
"engines": {
- "node": ">=16.15.1 <=18.17.1",
- "npm": ">=8.11.0 <=9.6.7"
+ "node": ">=16.15.1 <=20.9.0",
+ "npm": ">=8.11.0 <=10.1.0"
},
"peerDependencies": {
"idb-keyval": "^6.2.1",
@@ -49296,6 +49404,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/thingies": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.12.0.tgz",
+ "integrity": "sha512-AiGqfYC1jLmJagbzQGuoZRM48JPsr9yB734a7K6wzr34NMhjUPrWSQrkF7ZBybf3yCerCL2Gcr02kMv4NmaZfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.18"
+ },
+ "peerDependencies": {
+ "tslib": "^2"
+ }
+ },
"node_modules/throat": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
@@ -51690,6 +51810,18 @@
"node": ">= 10"
}
},
+ "node_modules/webpack-dev-server/node_modules/memfs": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
+ "dev": true,
+ "dependencies": {
+ "fs-monkey": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/webpack-dev-server/node_modules/schema-utils": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
@@ -61813,6 +61945,15 @@
}
}
},
+ "memfs": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
+ "dev": true,
+ "requires": {
+ "fs-monkey": "^1.0.4"
+ }
+ },
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -64738,6 +64879,15 @@
}
}
},
+ "memfs": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
+ "dev": true,
+ "requires": {
+ "fs-monkey": "^1.0.4"
+ }
+ },
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -66601,15 +66751,6 @@
"version": "3.0.5",
"dev": true
},
- "@types/mock-fs": {
- "version": "4.13.1",
- "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz",
- "integrity": "sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==",
- "dev": true,
- "requires": {
- "@types/node": "*"
- }
- },
"@types/ms": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
@@ -74528,7 +74669,9 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-diff": {
- "version": "1.2.0",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true
},
"fast-equals": {
@@ -75059,6 +75202,15 @@
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
+ "memfs": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
+ "dev": true,
+ "requires": {
+ "fs-monkey": "^1.0.4"
+ }
+ },
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
@@ -75158,7 +75310,9 @@
}
},
"fs-monkey": {
- "version": "1.0.3",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz",
+ "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==",
"dev": true
},
"fs-write-stream-atomic": {
@@ -76329,6 +76483,12 @@
}
}
},
+ "hyperdyperid": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
+ "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
+ "dev": true
+ },
"hyphenate-style-name": {
"version": "1.0.4"
},
@@ -79519,6 +79679,13 @@
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
"integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw=="
},
+ "lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
+ "dev": true,
+ "peer": true
+ },
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -80428,10 +80595,41 @@
}
},
"memfs": {
- "version": "3.4.7",
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.6.0.tgz",
+ "integrity": "sha512-I6mhA1//KEZfKRQT9LujyW6lRbX7RkC24xKododIDO3AGShcaFAMKElv1yFGWX8fD4UaSiwasr3NeQ5TdtHY1A==",
"dev": true,
"requires": {
- "fs-monkey": "^1.0.3"
+ "json-joy": "^9.2.0",
+ "thingies": "^1.11.1"
+ },
+ "dependencies": {
+ "arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true
+ },
+ "json-joy": {
+ "version": "9.9.1",
+ "resolved": "https://registry.npmjs.org/json-joy/-/json-joy-9.9.1.tgz",
+ "integrity": "sha512-/d7th2nbQRBQ/nqTkBe6KjjvDciSwn9UICmndwk3Ed/Bk9AqkTRm4PnLVfXG4DKbT0rEY0nKnwE7NqZlqKE6kg==",
+ "dev": true,
+ "requires": {
+ "arg": "^5.0.2",
+ "hyperdyperid": "^1.2.0"
+ }
+ },
+ "rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ }
}
},
"memoize-one": {
@@ -82086,12 +82284,6 @@
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
},
- "mock-fs": {
- "version": "4.14.0",
- "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz",
- "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==",
- "dev": true
- },
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -84053,6 +84245,18 @@
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
},
+ "quill-delta": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
+ "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "fast-diff": "^1.3.0",
+ "lodash.clonedeep": "^4.5.0",
+ "lodash.isequal": "^4.5.0"
+ }
+ },
"raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
@@ -84618,9 +84822,9 @@
}
},
"react-native-onyx": {
- "version": "1.0.115",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.115.tgz",
- "integrity": "sha512-uPrJcw3Ta/EFL3Mh3iUggZ7EeEwLTSSSc5iUkKAA+a9Y8kBo8+6MWup9VCM/4wgysZbf3VHUGJCWQ8H3vWKgUg==",
+ "version": "1.0.118",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz",
+ "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==",
"requires": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
@@ -88122,6 +88326,13 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "thingies": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.12.0.tgz",
+ "integrity": "sha512-AiGqfYC1jLmJagbzQGuoZRM48JPsr9yB734a7K6wzr34NMhjUPrWSQrkF7ZBybf3yCerCL2Gcr02kMv4NmaZfA==",
+ "dev": true,
+ "requires": {}
+ },
"throat": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
@@ -89919,6 +90130,15 @@
"integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==",
"dev": true
},
+ "memfs": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
+ "dev": true,
+ "requires": {
+ "fs-monkey": "^1.0.4"
+ }
+ },
"schema-utils": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
diff --git a/package.json b/package.json
index 4f6cf28831fe..b43685374dd1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.3.99-0",
+ "version": "1.4.0-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.",
@@ -139,7 +139,7 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.115",
+ "react-native-onyx": "1.0.118",
"react-native-pager-view": "^6.2.0",
"react-native-pdf": "^6.7.1",
"react-native-performance": "^5.1.0",
@@ -212,7 +212,6 @@
"@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",
"@types/react-beautiful-dnd": "^13.1.4",
@@ -260,8 +259,8 @@
"jest-cli": "29.4.1",
"jest-environment-jsdom": "^29.4.1",
"jest-transformer-svg": "^2.0.1",
+ "memfs": "^4.6.0",
"metro-react-native-babel-preset": "0.76.8",
- "mock-fs": "^4.13.0",
"onchange": "^7.1.0",
"portfinder": "^1.0.28",
"prettier": "^2.8.8",
@@ -302,7 +301,7 @@
]
},
"engines": {
- "node": "16.15.1",
- "npm": "8.11.0"
+ "node": ">=16.15.1 <=20.9.0",
+ "npm": ">=8.11.0 <=10.1.0"
}
}
diff --git a/patches/react-native-web+0.19.9+004+fix-pointer-events.patch b/patches/react-native-web+0.19.9+004+fix-pointer-events.patch
new file mode 100644
index 000000000000..a457fbcfe36c
--- /dev/null
+++ b/patches/react-native-web+0.19.9+004+fix-pointer-events.patch
@@ -0,0 +1,22 @@
+diff --git a/node_modules/react-native-web/dist/exports/StyleSheet/compiler/index.js b/node_modules/react-native-web/dist/exports/StyleSheet/compiler/index.js
+index bdcecc2..63f1364 100644
+--- a/node_modules/react-native-web/dist/exports/StyleSheet/compiler/index.js
++++ b/node_modules/react-native-web/dist/exports/StyleSheet/compiler/index.js
+@@ -353,7 +353,7 @@ function createAtomicRules(identifier, property, value) {
+ var _block2 = createDeclarationBlock({
+ pointerEvents: 'none'
+ });
+- rules.push(selector + ">*" + _block2);
++ rules.push(selector + " *" + _block2);
+ }
+ } else if (value === 'none' || value === 'box-none') {
+ finalValue = 'none!important';
+@@ -361,7 +361,7 @@ function createAtomicRules(identifier, property, value) {
+ var _block3 = createDeclarationBlock({
+ pointerEvents: 'auto'
+ });
+- rules.push(selector + ">*" + _block3);
++ rules.push(selector + " *" + _block3);
+ }
+ }
+ var _block4 = createDeclarationBlock({
diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh
index c67c37a527a2..88ab17e7a2bd 100755
--- a/scripts/build-desktop.sh
+++ b/scripts/build-desktop.sh
@@ -14,16 +14,15 @@ else
fi
SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}")
-LOCAL_PACKAGES=$(npm bin)
source "$SCRIPTS_DIR/shellUtils.sh";
title "Bundling Desktop js Bundle Using Webpack"
info " • ELECTRON_ENV: $ELECTRON_ENV"
info " • ENV file: $ENV_FILE"
info ""
-"$LOCAL_PACKAGES/webpack" --config config/webpack/webpack.desktop.js --env envFile=$ENV_FILE
+npx webpack --config config/webpack/webpack.desktop.js --env envFile=$ENV_FILE
title "Building Desktop App Archive Using Electron"
info ""
shift 1
-"$LOCAL_PACKAGES/electron-builder" --config config/electronBuilder.config.js "$@"
+npx electron-builder --config config/electronBuilder.config.js "$@"
diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js
index 566b6c709423..ec4ddd623929 100644
--- a/src/components/AddPlaidBankAccount.js
+++ b/src/components/AddPlaidBankAccount.js
@@ -9,8 +9,8 @@ import useNetwork from '@hooks/useNetwork';
import KeyboardShortcut from '@libs/KeyboardShortcut';
import Log from '@libs/Log';
import {plaidDataPropTypes} from '@pages/ReimbursementAccount/plaidDataPropTypes';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import * as App from '@userActions/App';
import * as BankAccounts from '@userActions/BankAccounts';
import CONST from '@src/CONST';
@@ -83,6 +83,8 @@ function AddPlaidBankAccount({
allowDebit,
isPlaidDisabled,
}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const subscribedKeyboardShortcuts = useRef([]);
const previousNetworkState = useRef();
@@ -186,7 +188,7 @@ function AddPlaidBankAccount({
{lodashGet(plaidData, 'isLoading') && (
diff --git a/src/components/AddressSearch/CurrentLocationButton.js b/src/components/AddressSearch/CurrentLocationButton.js
index 326b82d31e8f..3c7feb8fb70c 100644
--- a/src/components/AddressSearch/CurrentLocationButton.js
+++ b/src/components/AddressSearch/CurrentLocationButton.js
@@ -7,8 +7,8 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import useLocalize from '@hooks/useLocalize';
import getButtonState from '@libs/getButtonState';
import colors from '@styles/colors';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
const propTypes = {
/** Callback that runs when location button is clicked */
@@ -24,6 +24,7 @@ const defaultProps = {
};
function CurrentLocationButton({onPress, isDisabled}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
return (
diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js
index 61460a93650e..73472beeb48d 100644
--- a/src/components/AddressSearch/index.js
+++ b/src/components/AddressSearch/index.js
@@ -14,9 +14,9 @@ import * as ApiUtils from '@libs/ApiUtils';
import compose from '@libs/compose';
import getCurrentPosition from '@libs/getCurrentPosition';
import * as GooglePlacesUtils from '@libs/GooglePlacesUtils';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import CurrentLocationButton from './CurrentLocationButton';
@@ -144,6 +144,8 @@ const defaultProps = {
// Relevant thread: https://expensify.slack.com/archives/C03TQ48KC/p1634088400387400
// Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839
function AddressSearch(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const [displayListViewBorder, setDisplayListViewBorder] = useState(false);
const [isTyping, setIsTyping] = useState(false);
const [isFocused, setIsFocused] = useState(false);
@@ -392,7 +394,7 @@ function AddressSearch(props) {
listLoaderComponent={
@@ -489,8 +491,8 @@ function AddressSearch(props) {
}}
numberOfLines={2}
isRowScrollable={false}
- listHoverColor={themeColors.border}
- listUnderlayColor={themeColors.buttonPressedBG}
+ listHoverColor={theme.border}
+ listUnderlayColor={theme.buttonPressedBG}
onLayout={(event) => {
// We use the height of the element to determine if we should hide the border of the listView dropdown
// to prevent a lingering border when there are no address suggestions.
diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js
index 43fd5e6a1b98..bd88712432a8 100644
--- a/src/components/AmountTextInput.js
+++ b/src/components/AmountTextInput.js
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import refPropTypes from './refPropTypes';
import TextInput from './TextInput';
@@ -39,6 +39,7 @@ const defaultProps = {
};
function AmountTextInput(props) {
+ const styles = useThemeStyles();
return (
() => {
ReportActionContextMenu.hideContextMenu();
diff --git a/src/components/AnonymousReportFooter.js b/src/components/AnonymousReportFooter.js
index 2dc4159d1627..387e2ab01930 100644
--- a/src/components/AnonymousReportFooter.js
+++ b/src/components/AnonymousReportFooter.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {Text, View} from 'react-native';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Session from '@userActions/Session';
import AvatarWithDisplayName from './AvatarWithDisplayName';
import Button from './Button';
@@ -29,6 +29,7 @@ const defaultProps = {
};
function AnonymousReportFooter(props) {
+ const styles = useThemeStyles();
return (
diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js
index 52484355a242..b1fac827d273 100644
--- a/src/components/ArchivedReportFooter.js
+++ b/src/components/ArchivedReportFooter.js
@@ -9,7 +9,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import personalDetailsPropType from '@pages/personalDetailsPropType';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import Banner from './Banner';
@@ -50,6 +50,7 @@ const defaultProps = {
};
function ArchivedReportFooter(props) {
+ const styles = useThemeStyles();
const archiveReason = lodashGet(props.reportClosedAction, 'originalMessage.reason', CONST.REPORT.ARCHIVE_REASON.DEFAULT);
let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetails, [props.report.ownerAccountID, 'displayName']);
diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js
index f82fec156f9f..4ab81ae462c9 100755
--- a/src/components/AttachmentModal.js
+++ b/src/components/AttachmentModal.js
@@ -19,11 +19,10 @@ import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import useNativeDriver from '@libs/useNativeDriver';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
-import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -111,6 +110,8 @@ const defaultProps = {
};
function AttachmentModal(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const onModalHideCallbackRef = useRef(null);
const [isModalOpen, setIsModalOpen] = useState(props.defaultOpen);
const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false);
@@ -359,12 +360,8 @@ function AttachmentModal(props) {
}
const menuItems = [];
const parentReportAction = props.parentReportActions[props.report.parentReportActionID];
- const isDeleted = ReportActionsUtils.isDeletedAction(parentReportAction);
- const isSettled = ReportUtils.isSettled(props.parentReport.reportID);
- const isAdmin = Policy.isAdminOfFreePolicy([props.policy]) && ReportUtils.isExpenseReport(props.parentReport);
- const isRequestor = ReportUtils.isMoneyRequestReport(props.parentReport) && lodashGet(props.session, 'accountID', null) === parentReportAction.actorAccountID;
- const canEdit = !isSettled && !isDeleted && (isAdmin || isRequestor);
+ const canEdit = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.parentReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT);
if (canEdit) {
menuItems.push({
icon: Expensicons.Camera,
@@ -411,7 +408,7 @@ function AttachmentModal(props) {
onSubmit={submitAndClose}
onClose={closeModal}
isVisible={isModalOpen}
- backgroundColor={themeColors.componentBG}
+ backgroundColor={theme.componentBG}
onModalShow={() => {
props.onModalShow();
setShouldLoadAttachment(true);
diff --git a/src/components/AttachmentPicker/index.native.js b/src/components/AttachmentPicker/index.native.js
index 0e723d4cf048..5b955ee69dd3 100644
--- a/src/components/AttachmentPicker/index.native.js
+++ b/src/components/AttachmentPicker/index.native.js
@@ -14,7 +14,7 @@ import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import useLocalize from '@hooks/useLocalize';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as FileUtils from '@libs/fileDownload/FileUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import {defaultProps as baseDefaultProps, propTypes as basePropTypes} from './attachmentPickerPropTypes';
import launchCamera from './launchCamera';
@@ -101,6 +101,7 @@ const getDataForUpload = (fileData) => {
* @returns {JSX.Element}
*/
function AttachmentPicker({type, children, shouldHideCameraOption}) {
+ const styles = useThemeStyles();
const [isVisible, setIsVisible] = useState(false);
const completeAttachmentSelection = useRef();
diff --git a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js
index 673bb7c224e2..dd2713a38b2b 100644
--- a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js
+++ b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {PixelRatio, View} from 'react-native';
import useWindowDimensions from '@hooks/useWindowDimensions';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
const propTypes = {
/** Cell Container styles */
@@ -14,6 +14,7 @@ const defaultProps = {
};
function AttachmentCarouselCellRenderer(props) {
+ const styles = useThemeStyles();
const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, true);
const style = [props.style, styles.h100, {width: PixelRatio.roundToNearestPixel(windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2)}];
diff --git a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js
index f11bbcc9b187..14a6ea268468 100644
--- a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js
+++ b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js
@@ -8,8 +8,8 @@ import * as Expensicons from '@components/Icon/Expensicons';
import Tooltip from '@components/Tooltip';
import useLocalize from '@hooks/useLocalize';
import useWindowDimensions from '@hooks/useWindowDimensions';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
const propTypes = {
/** Where the arrows should be visible */
@@ -36,6 +36,8 @@ const defaultProps = {
};
function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward, cancelAutoHideArrow, autoHideArrow}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const isBackDisabled = page === 0;
const isForwardDisabled = page === _.size(attachments) - 1;
@@ -51,7 +53,7 @@ function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward
small
innerStyles={[styles.arrowIcon]}
icon={Expensicons.BackArrow}
- iconFill={themeColors.text}
+ iconFill={theme.text}
iconStyles={[styles.mr0]}
onPress={onBack}
onPressIn={cancelAutoHideArrow}
@@ -67,7 +69,7 @@ function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward
small
innerStyles={[styles.arrowIcon]}
icon={Expensicons.ArrowRight}
- iconFill={themeColors.text}
+ iconFill={theme.text}
iconStyles={[styles.mr0]}
onPress={onForward}
onPressIn={cancelAutoHideArrow}
diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js
index 38f70057be61..b6cc0cbf21a4 100644
--- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js
+++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js
@@ -9,7 +9,7 @@ import SafeAreaConsumer from '@components/SafeAreaConsumer';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import ReportAttachmentsContext from '@pages/home/report/ReportAttachmentsContext';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -49,6 +49,7 @@ const defaultProps = {
};
function CarouselItem({item, isFocused, onPress}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const {isAttachmentHidden} = useContext(ReportAttachmentsContext);
// eslint-disable-next-line es/no-nullish-coalescing-operators
diff --git a/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js b/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js
index 0839462d4f23..cc1e20cb44e0 100644
--- a/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js
+++ b/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js
@@ -15,7 +15,7 @@ import Animated, {
withDecay,
withSpring,
} from 'react-native-reanimated';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import AttachmentCarouselPagerContext from './AttachmentCarouselPagerContext';
import ImageWrapper from './ImageWrapper';
@@ -60,6 +60,7 @@ const imageTransformerDefaultProps = {
};
function ImageTransformer({imageWidth, imageHeight, imageScaleX, imageScaleY, scaledImageWidth, scaledImageHeight, isActive, children}) {
+ const styles = useThemeStyles();
const {canvasWidth, canvasHeight, onTap, onSwipe, onSwipeSuccess, pagerRef, shouldPagerScroll, isScrolling, onPinchGestureChange} = useContext(AttachmentCarouselPagerContext);
const minImageScale = useMemo(() => Math.min(imageScaleX, imageScaleY), [imageScaleX, imageScaleY]);
diff --git a/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js b/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js
index 3a27d80c5509..b0a8b1f0d083 100644
--- a/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js
+++ b/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js
@@ -2,13 +2,14 @@ import PropTypes from 'prop-types';
import React from 'react';
import {StyleSheet} from 'react-native';
import Animated from 'react-native-reanimated';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
const imageWrapperPropTypes = {
children: PropTypes.node.isRequired,
};
function ImageWrapper({children}) {
+ const styles = useThemeStyles();
return (
diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js
index c024b025c80e..27790121aab0 100644
--- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js
+++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js
@@ -3,8 +3,8 @@ import React, {useEffect, useRef} from 'react';
import {FlatList} from 'react-native-gesture-handler';
import Animated, {Easing, FadeOutDown, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import {propTypes} from './autoCompleteSuggestionsPropTypes';
@@ -29,6 +29,7 @@ const measureHeightOfSuggestionRows = (numRows, isSuggestionPickerLarge) => {
};
function BaseAutoCompleteSuggestions(props) {
+ const styles = useThemeStyles();
const rowHeight = useSharedValue(0);
const scrollRef = useRef(null);
/**
diff --git a/src/components/AutoEmailLink.js b/src/components/AutoEmailLink.js
index eece1a16ca5a..bffd2493aa5d 100644
--- a/src/components/AutoEmailLink.js
+++ b/src/components/AutoEmailLink.js
@@ -2,7 +2,7 @@ import {CONST} from 'expensify-common/lib/CONST';
import PropTypes from 'prop-types';
import React from 'react';
import _ from 'underscore';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import Text from './Text';
import TextLink from './TextLink';
@@ -22,6 +22,7 @@ const defaultProps = {
*/
function AutoEmailLink(props) {
+ const styles = useThemeStyles();
return (
{_.map(props.text.split(CONST.REG_EXP.EXTRACT_EMAIL), (str, index) => {
diff --git a/src/components/AutoUpdateTime.js b/src/components/AutoUpdateTime.js
index c85f14ed2c29..1970839ec320 100644
--- a/src/components/AutoUpdateTime.js
+++ b/src/components/AutoUpdateTime.js
@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import DateUtils from '@libs/DateUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import Text from './Text';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
@@ -23,6 +23,7 @@ const propTypes = {
};
function AutoUpdateTime(props) {
+ const styles = useThemeStyles();
/**
* @returns {Date} Returns the locale Date object
*/
diff --git a/src/components/Avatar.js b/src/components/Avatar.js
index 5e414486cc70..0052400bf51a 100644
--- a/src/components/Avatar.js
+++ b/src/components/Avatar.js
@@ -5,9 +5,9 @@ import _ from 'underscore';
import useNetwork from '@hooks/useNetwork';
import * as ReportUtils from '@libs/ReportUtils';
import stylePropTypes from '@styles/stylePropTypes';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
@@ -55,13 +55,15 @@ const defaultProps = {
iconAdditionalStyles: [],
containerStyles: [],
size: CONST.AVATAR_SIZE.DEFAULT,
- fill: themeColors.icon,
+ fill: undefined,
fallbackIcon: Expensicons.FallbackAvatar,
type: CONST.ICON_TYPE_AVATAR,
name: '',
};
function Avatar(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const [imageError, setImageError] = useState(false);
useNetwork({onReconnect: () => setImageError(false)});
@@ -84,7 +86,7 @@ function Avatar(props) {
const iconStyle = props.imageStyles && props.imageStyles.length ? [StyleUtils.getAvatarStyle(props.size), styles.bgTransparent, ...props.imageStyles] : undefined;
- const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill;
+ const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name).fill : props.fill || theme.icon;
const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(props.name) : props.fallbackIcon || Expensicons.FallbackAvatar;
return (
@@ -95,11 +97,11 @@ function Avatar(props) {
src={imageError ? fallbackAvatar : props.source}
height={iconSize}
width={iconSize}
- fill={imageError ? themeColors.offline : iconFillColor}
+ fill={imageError ? theme.offline : iconFillColor}
additionalStyles={[
StyleUtils.getAvatarBorderStyle(props.size, props.type),
isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(props.name) : {},
- imageError ? StyleUtils.getBackgroundColorStyle(themeColors.fallbackIconColor) : {},
+ imageError ? StyleUtils.getBackgroundColorStyle(theme.fallbackIconColor) : {},
...props.iconAdditionalStyles,
]}
/>
diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js
index 9b2b92aa9cee..a37f228a0d0d 100644
--- a/src/components/AvatarCropModal/AvatarCropModal.js
+++ b/src/components/AvatarCropModal/AvatarCropModal.js
@@ -17,9 +17,9 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
import compose from '@libs/compose';
import cropOrRotateImage from '@libs/cropOrRotateImage';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ImageCropView from './ImageCropView';
import Slider from './Slider';
@@ -61,6 +61,8 @@ const defaultProps = {
// This component can't be written using class since reanimated API uses hooks.
function AvatarCropModal(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const originalImageWidth = useSharedValue(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE);
const originalImageHeight = useSharedValue(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE);
const translateY = useSharedValue(0);
@@ -381,7 +383,7 @@ function AvatarCropModal(props) {
{/* To avoid layout shift we should hide this component until the image container & image is initialized */}
{!isImageInitialized || !isImageContainerInitialized ? (
@@ -402,8 +404,9 @@ function AvatarCropModal(props) {
+
diff --git a/src/components/AvatarCropModal/ImageCropView.js b/src/components/AvatarCropModal/ImageCropView.js
index cb135cc76c69..a50409da64f4 100644
--- a/src/components/AvatarCropModal/ImageCropView.js
+++ b/src/components/AvatarCropModal/ImageCropView.js
@@ -6,8 +6,8 @@ import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import ControlSelection from '@libs/ControlSelection';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import gestureHandlerPropTypes from './gestureHandlerPropTypes';
const propTypes = {
@@ -50,6 +50,7 @@ const defaultProps = {
};
function ImageCropView(props) {
+ const styles = useThemeStyles();
const containerStyle = StyleUtils.getWidthAndHeightStyle(props.containerSize, props.containerSize);
const originalImageHeight = props.originalImageHeight;
diff --git a/src/components/AvatarCropModal/Slider.js b/src/components/AvatarCropModal/Slider.js
index 4281da1e7b99..9df6ac3c0498 100644
--- a/src/components/AvatarCropModal/Slider.js
+++ b/src/components/AvatarCropModal/Slider.js
@@ -6,7 +6,7 @@ import Animated, {useAnimatedStyle} from 'react-native-reanimated';
import Tooltip from '@components/Tooltip';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import ControlSelection from '@libs/ControlSelection';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import gestureHandlerPropTypes from './gestureHandlerPropTypes';
const propTypes = {
@@ -26,6 +26,7 @@ const defaultProps = {
// This component can't be written using class since reanimated API uses hooks.
function Slider(props) {
+ const styles = useThemeStyles();
const sliderValue = props.sliderValue;
const [tooltipIsVisible, setTooltipIsVisible] = useState(true);
diff --git a/src/components/AvatarSkeleton.js b/src/components/AvatarSkeleton.js
index 2a633833f228..d2706447f756 100644
--- a/src/components/AvatarSkeleton.js
+++ b/src/components/AvatarSkeleton.js
@@ -1,15 +1,16 @@
import React from 'react';
import {Circle} from 'react-native-svg';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
import SkeletonViewContentLoader from './SkeletonViewContentLoader';
function AvatarSkeleton() {
+ const theme = useTheme();
return (
{
};
function AvatarWithDisplayName(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const title = ReportUtils.getReportName(props.report);
const subtitle = ReportUtils.getChatRoomSubtitle(props.report);
const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(props.report);
@@ -99,7 +101,7 @@ function AvatarWithDisplayName(props) {
const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(props.report);
const isExpenseRequest = ReportUtils.isExpenseRequest(props.report);
const defaultSubscriptSize = isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : props.size;
- const avatarBorderColor = props.isAnonymous ? themeColors.highlightBG : themeColors.componentBG;
+ const avatarBorderColor = props.isAnonymous ? theme.highlightBG : theme.componentBG;
const headerView = (
diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js
index 87bd382e806b..893a02288e77 100644
--- a/src/components/AvatarWithImagePicker.js
+++ b/src/components/AvatarWithImagePicker.js
@@ -9,8 +9,6 @@ import * as FileUtils from '@libs/fileDownload/FileUtils';
import getImageResolution from '@libs/fileDownload/getImageResolution';
import SpinningIndicatorAnimation from '@styles/animation/SpinningIndicatorAnimation';
import stylePropTypes from '@styles/stylePropTypes';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import AttachmentModal from './AttachmentModal';
@@ -26,6 +24,8 @@ import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import Tooltip from './Tooltip/PopoverAnchorTooltip';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import withNavigationFocus from './withNavigationFocus';
+import withTheme, {withThemePropTypes} from './withTheme';
+import withThemeStyles, {withThemeStylesPropTypes} from './withThemeStyles';
const propTypes = {
/** Avatar source to display */
@@ -95,6 +95,8 @@ const propTypes = {
isFocused: PropTypes.bool.isRequired,
...withLocalizePropTypes,
+ ...withThemeStylesPropTypes,
+ ...withThemePropTypes,
};
const defaultProps = {
@@ -253,8 +255,8 @@ class AvatarWithImagePicker extends React.Component {
const additionalStyles = _.isArray(this.props.style) ? this.props.style : [this.props.style];
return (
-
-
+
+
{this.props.source ? (
)}
-
+
@@ -364,7 +366,7 @@ class AvatarWithImagePicker extends React.Component {
{this.state.validationError && (
@@ -386,4 +388,4 @@ class AvatarWithImagePicker extends React.Component {
AvatarWithImagePicker.propTypes = propTypes;
AvatarWithImagePicker.defaultProps = defaultProps;
-export default compose(withLocalize, withNavigationFocus)(AvatarWithImagePicker);
+export default compose(withLocalize, withNavigationFocus, withThemeStyles, withTheme)(AvatarWithImagePicker);
diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js
index 05ca65fc64da..f3607b69a73f 100644
--- a/src/components/AvatarWithIndicator.js
+++ b/src/components/AvatarWithIndicator.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import * as UserUtils from '@libs/UserUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import Avatar from './Avatar';
import AvatarSkeleton from './AvatarSkeleton';
import * as Expensicons from './Icon/Expensicons';
@@ -30,6 +30,7 @@ const defaultProps = {
};
function AvatarWithIndicator(props) {
+ const styles = useThemeStyles();
return (
diff --git a/src/components/Badge.js b/src/components/Badge.js
deleted file mode 100644
index 49b330ae37b2..000000000000
--- a/src/components/Badge.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {View} from 'react-native';
-import styles from '@styles/styles';
-import * as StyleUtils from '@styles/StyleUtils';
-import CONST from '@src/CONST';
-import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
-import Text from './Text';
-
-const propTypes = {
- /** Is Success type */
- success: PropTypes.bool,
-
- /** Is Error type */
- error: PropTypes.bool,
-
- /** Whether badge is clickable */
- pressable: PropTypes.bool,
-
- /** Text to display in the Badge */
- text: PropTypes.string.isRequired,
-
- /** Text to display in the Badge */
- environment: PropTypes.string,
-
- /** Styles for Badge */
- // eslint-disable-next-line react/forbid-prop-types
- badgeStyles: PropTypes.arrayOf(PropTypes.object),
-
- /** Styles for Badge Text */
- // eslint-disable-next-line react/forbid-prop-types
- textStyles: PropTypes.arrayOf(PropTypes.object),
-
- /** Callback to be called on onPress */
- onPress: PropTypes.func,
-};
-
-const defaultProps = {
- success: false,
- error: false,
- pressable: false,
- badgeStyles: [],
- textStyles: [],
- onPress: undefined,
- environment: CONST.ENVIRONMENT.DEV,
-};
-
-function Badge(props) {
- const textStyles = props.success || props.error ? styles.textWhite : undefined;
- const Wrapper = props.pressable ? PressableWithoutFeedback : View;
- const wrapperStyles = ({pressed}) => [
- styles.badge,
- styles.ml2,
- StyleUtils.getBadgeColorStyle(props.success, props.error, pressed, props.environment === CONST.ENVIRONMENT.ADHOC),
- ...props.badgeStyles,
- ];
-
- return (
-
-
- {props.text}
-
-
- );
-}
-
-Badge.displayName = 'Badge';
-Badge.propTypes = propTypes;
-Badge.defaultProps = defaultProps;
-export default Badge;
diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx
new file mode 100644
index 000000000000..22c056dfdfc4
--- /dev/null
+++ b/src/components/Badge.tsx
@@ -0,0 +1,66 @@
+import React, {useCallback} from 'react';
+import {GestureResponderEvent, PressableStateCallbackType, StyleProp, TextStyle, View, ViewStyle} from 'react-native';
+import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
+import CONST from '@src/CONST';
+import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
+import Text from './Text';
+
+type BadgeProps = {
+ /** Is Success type */
+ success?: boolean;
+
+ /** Is Error type */
+ error?: boolean;
+
+ /** Whether badge is clickable */
+ pressable?: boolean;
+
+ /** Text to display in the Badge */
+ text: string;
+
+ /** Text to display in the Badge */
+ environment?: string;
+
+ /** Styles for Badge */
+ badgeStyles?: StyleProp;
+
+ /** Styles for Badge Text */
+ textStyles?: StyleProp;
+
+ /** Callback to be called on onPress */
+ onPress: (event?: GestureResponderEvent | KeyboardEvent) => void;
+};
+
+function Badge({success = false, error = false, pressable = false, text, environment = CONST.ENVIRONMENT.DEV, badgeStyles, textStyles, onPress = () => {}}: BadgeProps) {
+ const styles = useThemeStyles();
+ const textColorStyles = success || error ? styles.textWhite : undefined;
+ const Wrapper = pressable ? PressableWithoutFeedback : View;
+
+ const wrapperStyles: (state: PressableStateCallbackType) => StyleProp = useCallback(
+ ({pressed}) => [styles.badge, styles.ml2, StyleUtils.getBadgeColorStyle(success, error, pressed, environment === CONST.ENVIRONMENT.ADHOC), badgeStyles],
+ [styles.badge, styles.ml2, success, error, environment, badgeStyles],
+ );
+
+ return (
+
+
+ {text}
+
+
+ );
+}
+
+Badge.displayName = 'Badge';
+
+export default Badge;
diff --git a/src/components/Banner.js b/src/components/Banner.js
index 23226e21eb51..2fcb866334e0 100644
--- a/src/components/Banner.js
+++ b/src/components/Banner.js
@@ -3,8 +3,8 @@ import React, {memo} from 'react';
import {View} from 'react-native';
import compose from '@libs/compose';
import getButtonState from '@libs/getButtonState';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import Hoverable from './Hoverable';
import Icon from './Icon';
@@ -56,6 +56,7 @@ const defaultProps = {
};
function Banner(props) {
+ const styles = useThemeStyles();
return (
{(isHovered) => {
diff --git a/src/components/BaseMiniContextMenuItem.js b/src/components/BaseMiniContextMenuItem.js
index b8d7a4a7484b..04a569ba7f36 100644
--- a/src/components/BaseMiniContextMenuItem.js
+++ b/src/components/BaseMiniContextMenuItem.js
@@ -5,8 +5,8 @@ import _ from 'underscore';
import DomUtils from '@libs/DomUtils';
import getButtonState from '@libs/getButtonState';
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import Tooltip from './Tooltip/PopoverAnchorTooltip';
@@ -50,6 +50,7 @@ const defaultProps = {
* @returns {JSX.Element}
*/
function BaseMiniContextMenuItem(props) {
+ const styles = useThemeStyles();
return (
diff --git a/src/components/BlockingViews/FullPageNotFoundView.js b/src/components/BlockingViews/FullPageNotFoundView.js
index 5232b5eca8dd..b82474aa0694 100644
--- a/src/components/BlockingViews/FullPageNotFoundView.js
+++ b/src/components/BlockingViews/FullPageNotFoundView.js
@@ -5,7 +5,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Illustrations from '@components/Icon/Illustrations';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import ROUTES from '@src/ROUTES';
import BlockingView from './BlockingView';
@@ -53,6 +53,7 @@ const defaultProps = {
// eslint-disable-next-line rulesdir/no-negated-variables
function FullPageNotFoundView({children, shouldShow, titleKey, subtitleKey, linkKey, onBackButtonPress, shouldShowLink, shouldShowBackButton, onLinkPress}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
if (shouldShow) {
return (
diff --git a/src/components/Button/index.js b/src/components/Button/index.js
index 5fe7dd1fe812..b9aaf8868924 100644
--- a/src/components/Button/index.js
+++ b/src/components/Button/index.js
@@ -10,9 +10,9 @@ import Text from '@components/Text';
import withNavigationFallback from '@components/withNavigationFallback';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import HapticFeedback from '@libs/HapticFeedback';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import validateSubmitShortcut from './validateSubmitShortcut';
@@ -127,7 +127,7 @@ const defaultProps = {
shouldShowRightIcon: false,
icon: null,
iconRight: Expensicons.ArrowRight,
- iconFill: themeColors.textLight,
+ iconFill: undefined,
iconStyles: [],
iconRightStyles: [],
isLoading: false,
@@ -201,6 +201,8 @@ function Button({
accessibilityLabel,
forwardedRef,
}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const isFocused = useIsFocused();
const keyboardShortcutCallback = useCallback(
@@ -254,7 +256,7 @@ function Button({
@@ -265,7 +267,7 @@ function Button({
@@ -334,7 +336,7 @@ function Button({
{renderContent()}
{isLoading && (
)}
diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.js
index 7c88d9202b78..15f2e2f4d6de 100644
--- a/src/components/ButtonWithDropdownMenu.js
+++ b/src/components/ButtonWithDropdownMenu.js
@@ -3,9 +3,9 @@ import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import useWindowDimensions from '@hooks/useWindowDimensions';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import Button from './Button';
import Icon from './Icon';
@@ -72,6 +72,8 @@ const defaultProps = {
};
function ButtonWithDropdownMenu(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const [selectedItemIndex, setSelectedItemIndex] = useState(0);
const [isMenuVisible, setIsMenuVisible] = useState(false);
const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null);
@@ -134,7 +136,7 @@ function ButtonWithDropdownMenu(props) {
diff --git a/src/components/CardPreview.js b/src/components/CardPreview.js
index 9f59ca140ce5..df944d930a92 100644
--- a/src/components/CardPreview.js
+++ b/src/components/CardPreview.js
@@ -4,7 +4,7 @@ import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import ExpensifyCardImage from '@assets/images/expensify-card.svg';
import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import ONYXKEYS from '@src/ONYXKEYS';
import Text from './Text';
@@ -33,6 +33,7 @@ const defaultProps = {
};
function CardPreview({privatePersonalDetails: {legalFirstName, legalLastName}, session: {email}}) {
+ const styles = useThemeStyles();
usePrivatePersonalDetails();
const cardHolder = legalFirstName && legalLastName ? `${legalFirstName} ${legalLastName}` : email;
diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js
index 156007aea76e..ff7087df91dd 100644
--- a/src/components/CategoryPicker/index.js
+++ b/src/components/CategoryPicker/index.js
@@ -5,12 +5,13 @@ import _ from 'underscore';
import OptionsSelector from '@components/OptionsSelector';
import useLocalize from '@hooks/useLocalize';
import * as OptionsListUtils from '@libs/OptionsListUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {defaultProps, propTypes} from './categoryPickerPropTypes';
function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const [searchValue, setSearchValue] = useState('');
diff --git a/src/components/Checkbox.js b/src/components/Checkbox.js
index 5734ad2fed26..4b9ce922aacb 100644
--- a/src/components/Checkbox.js
+++ b/src/components/Checkbox.js
@@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import stylePropTypes from '@styles/stylePropTypes';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
@@ -67,6 +67,8 @@ const defaultProps = {
};
function Checkbox(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const handleSpaceKey = (event) => {
if (event.code !== 'Space') {
return;
@@ -115,7 +117,7 @@ function Checkbox(props) {
{props.isChecked && (
diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js
index 86dba1d2a932..146e37ceb730 100644
--- a/src/components/CheckboxWithLabel.js
+++ b/src/components/CheckboxWithLabel.js
@@ -2,11 +2,12 @@ import PropTypes from 'prop-types';
import React, {useState} from 'react';
import {View} from 'react-native';
import _ from 'underscore';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import Checkbox from './Checkbox';
import FormHelpMessage from './FormHelpMessage';
import PressableWithFeedback from './Pressable/PressableWithFeedback';
+import refPropTypes from './refPropTypes';
import Text from './Text';
/**
@@ -54,7 +55,7 @@ const propTypes = {
defaultValue: PropTypes.bool,
/** React ref being forwarded to the Checkbox input */
- forwardedRef: PropTypes.func,
+ forwardedRef: refPropTypes,
/** The ID used to uniquely identify the input in a Form */
/* eslint-disable-next-line react/no-unused-prop-types */
@@ -83,6 +84,7 @@ const defaultProps = {
};
function CheckboxWithLabel(props) {
+ const styles = useThemeStyles();
// We need to pick the first value that is strictly a boolean
// https://github.com/Expensify/App/issues/16885#issuecomment-1520846065
const [isChecked, setIsChecked] = useState(() => _.find([props.value, props.defaultValue, props.isChecked], (value) => _.isBoolean(value)));
diff --git a/src/components/CommunicationsLink.js b/src/components/CommunicationsLink.js
index f09fecea5239..dbbe5737b3aa 100644
--- a/src/components/CommunicationsLink.js
+++ b/src/components/CommunicationsLink.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import Clipboard from '@libs/Clipboard';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import ContextMenuItem from './ContextMenuItem';
import * as Expensicons from './Icon/Expensicons';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
@@ -26,6 +26,7 @@ const defaultProps = {
};
function CommunicationsLink(props) {
+ const styles = useThemeStyles();
return (
diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js
index d02fdd2563b1..4c61a5b5bba5 100755
--- a/src/components/Composer/index.js
+++ b/src/components/Composer/index.js
@@ -16,9 +16,9 @@ import updateIsFullComposerAvailable from '@libs/ComposerUtils/updateIsFullCompo
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition';
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -57,7 +57,7 @@ const propTypes = {
isDisabled: PropTypes.bool,
/** Set focus to this component the first time it renders.
- Override this in case you need to set focus on one field out of many, or when you want to disable autoFocus */
+ Override this in case you need to set focus on one field out of many, or when you want to disable autoFocus */
autoFocus: PropTypes.bool,
/** Update selection position on change */
@@ -169,6 +169,8 @@ function Composer({
isComposerFullSize,
...props
}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {windowWidth} = useWindowDimensions();
const textRef = useRef(null);
const textInput = useRef(null);
@@ -448,7 +450,8 @@ function Composer({
StyleUtils.getComposeTextAreaPadding(numberOfLines, isComposerFullSize),
Browser.isMobileSafari() || Browser.isSafari() ? styles.rtlTextRenderForSafari : {},
],
- [style, maxLines, numberOfLines, isComposerFullSize],
+
+ [numberOfLines, maxLines, styles.overflowHidden, styles.rtlTextRenderForSafari, style, isComposerFullSize],
);
return (
@@ -456,7 +459,7 @@ function Composer({
(textInput.current = el)}
selection={selection}
style={inputStyleMemo}
diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js
index 6142322848d0..ff8ee4f861a4 100644
--- a/src/components/ConfirmContent.js
+++ b/src/components/ConfirmContent.js
@@ -4,7 +4,7 @@ import {View} from 'react-native';
import _ from 'underscore';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import Button from './Button';
import Header from './Header';
@@ -87,6 +87,7 @@ const defaultProps = {
};
function ConfirmContent(props) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
diff --git a/src/components/ConfirmationPage.js b/src/components/ConfirmationPage.js
index 22e29dca519d..ac56ea3d22e9 100644
--- a/src/components/ConfirmationPage.js
+++ b/src/components/ConfirmationPage.js
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import Button from './Button';
import FixedFooter from './FixedFooter';
import Lottie from './Lottie';
@@ -39,6 +39,7 @@ const defaultProps = {
};
function ConfirmationPage(props) {
+ const styles = useThemeStyles();
return (
<>
diff --git a/src/components/ConnectBankAccountButton.js b/src/components/ConnectBankAccountButton.js
index 2c66bcc200da..6afd3d57d4e6 100644
--- a/src/components/ConnectBankAccountButton.js
+++ b/src/components/ConnectBankAccountButton.js
@@ -3,7 +3,7 @@ import React from 'react';
import {View} from 'react-native';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as ReimbursementAccount from '@userActions/ReimbursementAccount';
import Button from './Button';
import * as Expensicons from './Icon/Expensicons';
@@ -30,6 +30,7 @@ const defaultProps = {
};
function ConnectBankAccountButton(props) {
+ const styles = useThemeStyles();
const activeRoute = Navigation.getActiveRouteWithoutParams();
return props.network.isOffline ? (
diff --git a/src/components/ContextMenuItem.js b/src/components/ContextMenuItem.js
index 80d4855392a4..d0a43badc5e3 100644
--- a/src/components/ContextMenuItem.js
+++ b/src/components/ContextMenuItem.js
@@ -4,8 +4,8 @@ import useThrottledButtonState from '@hooks/useThrottledButtonState';
import useWindowDimensions from '@hooks/useWindowDimensions';
import getButtonState from '@libs/getButtonState';
import getContextMenuItemStyles from '@styles/getContextMenuItemStyles';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import BaseMiniContextMenuItem from './BaseMiniContextMenuItem';
import Icon from './Icon';
import MenuItem from './MenuItem';
@@ -53,6 +53,7 @@ const defaultProps = {
};
function ContextMenuItem({onPress, successIcon, successText, icon, text, isMini, description, isAnonymousAction, isFocused, innerRef}) {
+ const styles = useThemeStyles();
const {windowWidth} = useWindowDimensions();
const [isThrottledButtonActive, setThrottledButtonInactive] = useThrottledButtonState();
diff --git a/src/components/CountrySelector.js b/src/components/CountrySelector.js
index c2426c5b7b0b..13fc215f1d8c 100644
--- a/src/components/CountrySelector.js
+++ b/src/components/CountrySelector.js
@@ -3,7 +3,7 @@ import React, {useEffect} from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import ROUTES from '@src/ROUTES';
import FormHelpMessage from './FormHelpMessage';
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
@@ -33,6 +33,7 @@ const defaultProps = {
};
function CountrySelector({errorText, value: countryCode, onInputChange, forwardedRef}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const title = countryCode ? translate(`allCountries.${countryCode}`) : '';
diff --git a/src/components/CurrencySymbolButton.js b/src/components/CurrencySymbolButton.js
index ca7816a9f117..4d43ec3d93e0 100644
--- a/src/components/CurrencySymbolButton.js
+++ b/src/components/CurrencySymbolButton.js
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import useLocalize from '@hooks/useLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import Text from './Text';
@@ -16,6 +16,7 @@ const propTypes = {
};
function CurrencySymbolButton({onCurrencyButtonPress, currencySymbol}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
return (
diff --git a/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx b/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx
index 3a87702b48e4..685db8031330 100644
--- a/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx
+++ b/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx
@@ -3,9 +3,9 @@ import {View} from 'react-native';
import {Circle, Rect} from 'react-native-svg';
import {ValueOf} from 'type-fest';
import SkeletonViewContentLoader from '@components/SkeletonViewContentLoader';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
@@ -23,12 +23,9 @@ type CurrentUserPersonalDetailsSkeletonViewProps = {
foregroundColor?: string;
};
-function CurrentUserPersonalDetailsSkeletonView({
- shouldAnimate = true,
- avatarSize = CONST.AVATAR_SIZE.LARGE,
- backgroundColor = themeColors.highlightBG,
- foregroundColor = themeColors.border,
-}: CurrentUserPersonalDetailsSkeletonViewProps) {
+function CurrentUserPersonalDetailsSkeletonView({shouldAnimate = true, avatarSize = CONST.AVATAR_SIZE.LARGE, backgroundColor, foregroundColor}: CurrentUserPersonalDetailsSkeletonViewProps) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const avatarPlaceholderSize = StyleUtils.getAvatarSize(avatarSize);
const avatarPlaceholderRadius = avatarPlaceholderSize / 2;
const spaceBetweenAvatarAndHeadline = styles.mb3.marginBottom + styles.mt1.marginTop + (variables.lineHeightXXLarge - variables.fontSizeXLarge) / 2;
@@ -39,8 +36,8 @@ function CurrentUserPersonalDetailsSkeletonView({
{formattedBalance};
}
diff --git a/src/components/CustomStatusBar/index.js b/src/components/CustomStatusBar/index.js
index 2ffd763bf088..a724c71059ef 100644
--- a/src/components/CustomStatusBar/index.js
+++ b/src/components/CustomStatusBar/index.js
@@ -1,23 +1,24 @@
import React, {useEffect} from 'react';
import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
import StatusBar from '@libs/StatusBar';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
function CustomStatusBar() {
+ const theme = useTheme();
useEffect(() => {
Navigation.isNavigationReady().then(() => {
// Set the status bar colour depending on the current route.
// If we don't have any colour defined for a route, fall back to
// appBG color.
const currentRoute = navigationRef.getCurrentRoute();
- let currentScreenBackgroundColor = themeColors.appBG;
- if (currentRoute && 'name' in currentRoute && currentRoute.name in themeColors.PAGE_BACKGROUND_COLORS) {
- currentScreenBackgroundColor = themeColors.PAGE_BACKGROUND_COLORS[currentRoute.name];
+ let currentScreenBackgroundColor = theme.appBG;
+ if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_BACKGROUND_COLORS) {
+ currentScreenBackgroundColor = theme.PAGE_BACKGROUND_COLORS[currentRoute.name];
}
StatusBar.setBarStyle('light-content', true);
StatusBar.setBackgroundColor(currentScreenBackgroundColor);
});
- }, []);
+ }, [theme.PAGE_BACKGROUND_COLORS, theme.appBG]);
return ;
}
diff --git a/src/components/DatePicker/index.android.js b/src/components/DatePicker/index.android.js
index 17d1e2e14e71..5e7086fb78ad 100644
--- a/src/components/DatePicker/index.android.js
+++ b/src/components/DatePicker/index.android.js
@@ -3,11 +3,12 @@ import {format, parseISO} from 'date-fns';
import React, {forwardRef, useCallback, useImperativeHandle, useRef, useState} from 'react';
import {Keyboard} from 'react-native';
import TextInput from '@components/TextInput';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import {defaultProps, propTypes} from './datepickerPropTypes';
const DatePicker = forwardRef(({value, defaultValue, label, placeholder, errorText, containerStyles, disabled, onBlur, onInputChange, maxDate, minDate}, outerRef) => {
+ const styles = useThemeStyles();
const ref = useRef();
const [isPickerVisible, setIsPickerVisible] = useState(false);
diff --git a/src/components/DatePicker/index.ios.js b/src/components/DatePicker/index.ios.js
index 8b884c29b07f..44a825aa8183 100644
--- a/src/components/DatePicker/index.ios.js
+++ b/src/components/DatePicker/index.ios.js
@@ -7,12 +7,14 @@ import Popover from '@components/Popover';
import TextInput from '@components/TextInput';
import useKeyboardState from '@hooks/useKeyboardState';
import useLocalize from '@hooks/useLocalize';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import {defaultProps, propTypes} from './datepickerPropTypes';
function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLocale, minDate, maxDate, label, disabled, onBlur, placeholder, containerStyles, errorText}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const dateValue = value || defaultValue;
const [isPickerVisible, setIsPickerVisible] = useState(false);
const [selectedDate, setSelectedDate] = useState(dateValue ? new Date(dateValue) : new Date());
@@ -104,12 +106,13 @@ function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLoca
+
diff --git a/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.js b/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.js
index 671744a3d59c..3c7366949ac1 100644
--- a/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.js
+++ b/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.js
@@ -10,8 +10,8 @@ import TextLink from '@components/TextLink';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -33,6 +33,8 @@ const defaultProps = {
};
function DeeplinkRedirectLoadingIndicator({translate, openLinkInBrowser, session}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
return (
@@ -56,7 +58,7 @@ function DeeplinkRedirectLoadingIndicator({translate, openLinkInBrowser, session
diff --git a/src/components/DisplayNames/DisplayNamesTooltipItem.tsx b/src/components/DisplayNames/DisplayNamesTooltipItem.tsx
index 8f215fefd71b..82f9c5799b78 100644
--- a/src/components/DisplayNames/DisplayNamesTooltipItem.tsx
+++ b/src/components/DisplayNames/DisplayNamesTooltipItem.tsx
@@ -3,12 +3,12 @@ import {Text as RNText, StyleProp, TextStyle} from 'react-native';
import Text from '@components/Text';
import UserDetailsTooltip from '@components/UserDetailsTooltip';
import {AvatarSource} from '@libs/UserUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
type DisplayNamesTooltipItemProps = {
index?: number;
- /** The full title of the DisplayNames component (not split up) */
+ /** The function to get a distance to shift the tooltip horizontally */
getTooltipShiftX?: (index: number) => number | undefined;
/** The Account ID for the tooltip */
@@ -40,6 +40,7 @@ function DisplayNamesTooltipItem({
textStyles = [],
childRefs = {current: []},
}: DisplayNamesTooltipItemProps) {
+ const styles = useThemeStyles();
const tooltipIndexBridge = useCallback(() => getTooltipShiftX(index), [getTooltipShiftX, index]);
return (
diff --git a/src/components/DisplayNames/DisplayNamesWithTooltip.tsx b/src/components/DisplayNames/DisplayNamesWithTooltip.tsx
index 43cfab23d46f..8c8720c7c99f 100644
--- a/src/components/DisplayNames/DisplayNamesWithTooltip.tsx
+++ b/src/components/DisplayNames/DisplayNamesWithTooltip.tsx
@@ -2,13 +2,14 @@ import React, {Fragment, useCallback, useRef} from 'react';
import {Text as RNText, View} from 'react-native';
import Text from '@components/Text';
import Tooltip from '@components/Tooltip';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import DisplayNamesTooltipItem from './DisplayNamesTooltipItem';
import DisplayNamesProps from './types';
type HTMLElementWithText = HTMLElement & RNText;
function DisplayNamesWithToolTip({shouldUseFullTitle, fullTitle, displayNamesWithTooltips, textStyles = [], numberOfLines = 1}: DisplayNamesProps) {
+ const styles = useThemeStyles();
const containerRef = useRef(null);
const childRefs = useRef([]);
const isEllipsisActive = !!containerRef.current?.offsetWidth && !!containerRef.current?.scrollWidth && containerRef.current.offsetWidth < containerRef.current.scrollWidth;
diff --git a/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx b/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx
index 8779a58cf4d6..1854ebe2353d 100644
--- a/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx
+++ b/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import {StyleProp, TextStyle} from 'react-native';
import Text from '@components/Text';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
type DisplayNamesWithoutTooltipProps = {
/** The full title of the DisplayNames component (not split up) */
@@ -15,6 +15,7 @@ type DisplayNamesWithoutTooltipProps = {
};
function DisplayNamesWithoutTooltip({textStyles = [], numberOfLines = 1, fullTitle = ''}: DisplayNamesWithoutTooltipProps) {
+ const styles = useThemeStyles();
return (
+
{translate('eReceipt.guaranteed')}
diff --git a/src/components/DistanceMapView/index.android.js b/src/components/DistanceMapView/index.android.js
index 358b2f483a2b..848167de653d 100644
--- a/src/components/DistanceMapView/index.android.js
+++ b/src/components/DistanceMapView/index.android.js
@@ -6,11 +6,12 @@ import * as Expensicons from '@components/Icon/Expensicons';
import MapView from '@components/MapView';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import * as distanceMapViewPropTypes from './distanceMapViewPropTypes';
function DistanceMapView(props) {
+ const styles = useThemeStyles();
const [isMapReady, setIsMapReady] = useState(false);
const {isOffline} = useNetwork();
const {translate} = useLocalize();
diff --git a/src/components/DistanceRequest/DistanceRequestFooter.js b/src/components/DistanceRequest/DistanceRequestFooter.js
index 4d4335781b72..f53fadb8ab87 100644
--- a/src/components/DistanceRequest/DistanceRequestFooter.js
+++ b/src/components/DistanceRequest/DistanceRequestFooter.js
@@ -13,8 +13,8 @@ import transactionPropTypes from '@components/transactionPropTypes';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import * as TransactionUtils from '@libs/TransactionUtils';
-import styles from '@styles/styles';
-import theme from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -55,6 +55,8 @@ const defaultProps = {
transaction: {},
};
function DistanceRequestFooter({waypoints, transaction, mapboxAccessToken, navigateToWaypointEditPage}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {isOffline} = useNetwork();
const {translate} = useLocalize();
@@ -94,7 +96,7 @@ function DistanceRequestFooter({waypoints, transaction, mapboxAccessToken, navig
}),
(waypoint) => waypoint,
),
- [waypoints, lastWaypointIndex],
+ [waypoints, lastWaypointIndex, theme.icon],
);
return (
diff --git a/src/components/DistanceRequest/DistanceRequestRenderItem.js b/src/components/DistanceRequest/DistanceRequestRenderItem.js
index 0ee45febb21a..1735e244a347 100644
--- a/src/components/DistanceRequest/DistanceRequestRenderItem.js
+++ b/src/components/DistanceRequest/DistanceRequestRenderItem.js
@@ -5,7 +5,7 @@ import _ from 'underscore';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import useLocalize from '@hooks/useLocalize';
-import theme from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
const propTypes = {
/** The waypoints for the distance request */
@@ -48,6 +48,7 @@ const defaultProps = {
};
function DistanceRequestRenderItem({waypoints, item, onSecondaryInteraction, getIndex, isActive, onPress, disabled}) {
+ const theme = useTheme();
const {translate} = useLocalize();
const numberOfWaypoints = _.size(waypoints);
const lastWaypointIndex = numberOfWaypoints - 1;
diff --git a/src/components/DistanceRequest/index.js b/src/components/DistanceRequest/index.js
index e702ab111921..6fa5dfede620 100644
--- a/src/components/DistanceRequest/index.js
+++ b/src/components/DistanceRequest/index.js
@@ -19,7 +19,7 @@ import * as IOUUtils from '@libs/IOUUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as TransactionUtils from '@libs/TransactionUtils';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import * as MapboxToken from '@userActions/MapboxToken';
import * as Transaction from '@userActions/Transaction';
@@ -65,6 +65,7 @@ const defaultProps = {
};
function DistanceRequest({transactionID, report, transaction, route, isEditingRequest, onSubmit}) {
+ const styles = useThemeStyles();
const {isOffline} = useNetwork();
const {translate} = useLocalize();
diff --git a/src/components/DotIndicatorMessage.js b/src/components/DotIndicatorMessage.js
index 26e01f0eee8a..ac0ac15f437d 100644
--- a/src/components/DotIndicatorMessage.js
+++ b/src/components/DotIndicatorMessage.js
@@ -4,9 +4,9 @@ import {View} from 'react-native';
import _ from 'underscore';
import * as Localize from '@libs/Localize';
import stylePropTypes from '@styles/stylePropTypes';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';
@@ -39,6 +39,8 @@ const defaultProps = {
};
function DotIndicatorMessage(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
if (_.isEmpty(props.messages)) {
return null;
}
@@ -65,7 +67,7 @@ function DotIndicatorMessage(props) {
diff --git a/src/components/DragAndDrop/NoDropZone/index.tsx b/src/components/DragAndDrop/NoDropZone/index.tsx
index 5c914b646e0c..9f2c700b8918 100644
--- a/src/components/DragAndDrop/NoDropZone/index.tsx
+++ b/src/components/DragAndDrop/NoDropZone/index.tsx
@@ -1,10 +1,11 @@
import React, {useRef} from 'react';
import {View} from 'react-native';
import useDragAndDrop from '@hooks/useDragAndDrop';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import type NoDropZoneProps from './types';
function NoDropZone({children}: NoDropZoneProps) {
+ const styles = useThemeStyles();
const noDropZone = useRef(null);
useDragAndDrop({
diff --git a/src/components/DragAndDrop/Provider/index.tsx b/src/components/DragAndDrop/Provider/index.tsx
index fbf9ffd30e80..761c512497ac 100644
--- a/src/components/DragAndDrop/Provider/index.tsx
+++ b/src/components/DragAndDrop/Provider/index.tsx
@@ -3,7 +3,7 @@ import Str from 'expensify-common/lib/str';
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
import {View} from 'react-native';
import useDragAndDrop from '@hooks/useDragAndDrop';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import type {DragAndDropContextParams, DragAndDropProviderProps, SetOnDropHandlerCallback} from './types';
const DragAndDropContext = React.createContext({});
@@ -13,6 +13,7 @@ function shouldAcceptDrop(event: DragEvent): boolean {
}
function DragAndDropProvider({children, isDisabled = false, setIsDraggingOver = () => {}}: DragAndDropProviderProps) {
+ const styles = useThemeStyles();
const dropZone = useRef(null);
const dropZoneID = useRef(Str.guid('drag-n-drop'));
diff --git a/src/components/DraggableList/index.native.tsx b/src/components/DraggableList/index.native.tsx
index d7e42a5f5525..e7ff058234b7 100644
--- a/src/components/DraggableList/index.native.tsx
+++ b/src/components/DraggableList/index.native.tsx
@@ -1,10 +1,11 @@
import React from 'react';
import DraggableFlatList from 'react-native-draggable-flatlist';
import {FlatList} from 'react-native-gesture-handler';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import type {DraggableListProps} from './types';
function DraggableList({renderClone, shouldUsePortal, ...viewProps}: DraggableListProps, ref: React.ForwardedRef>) {
+ const styles = useThemeStyles();
return (
(
}: DraggableListProps,
ref: React.ForwardedRef,
) {
+ const styles = useThemeStyles();
/**
* Function to be called when the user finishes dragging an item
* It will reorder the list and call the callback function
diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js
index 7c186216a1fa..85c753c7ccb3 100644
--- a/src/components/EReceipt.js
+++ b/src/components/EReceipt.js
@@ -6,8 +6,8 @@ import useLocalize from '@hooks/useLocalize';
import * as CardUtils from '@libs/CardUtils';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -30,6 +30,7 @@ const defaultProps = {
};
function EReceipt({transaction, transactionID}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
// Get receipt colorway, or default to Yellow.
diff --git a/src/components/EReceiptThumbnail.js b/src/components/EReceiptThumbnail.js
index fc69601983f8..f54e246b8b1e 100644
--- a/src/components/EReceiptThumbnail.js
+++ b/src/components/EReceiptThumbnail.js
@@ -3,8 +3,8 @@ import React, {useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -42,6 +42,7 @@ function getBackgroundImage(transaction) {
}
function EReceiptThumbnail({transaction}) {
+ const styles = useThemeStyles();
// Get receipt colorway, or default to Yellow.
const {backgroundColor: primaryColor, color: secondaryColor} = StyleUtils.getEReceiptColorStyles(StyleUtils.getEReceiptColorCode(transaction));
diff --git a/src/components/EmojiPicker/CategoryShortcutBar.js b/src/components/EmojiPicker/CategoryShortcutBar.js
index bab1e344da40..c0c9fb8ea161 100644
--- a/src/components/EmojiPicker/CategoryShortcutBar.js
+++ b/src/components/EmojiPicker/CategoryShortcutBar.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CategoryShortcutButton from './CategoryShortcutButton';
const propTypes = {
@@ -20,6 +20,7 @@ const propTypes = {
};
function CategoryShortcutBar(props) {
+ const styles = useThemeStyles();
return (
{_.map(props.headerEmojis, (headerEmoji, i) => (
diff --git a/src/components/EmojiPicker/CategoryShortcutButton.js b/src/components/EmojiPicker/CategoryShortcutButton.js
index 3b12afce0d08..6ced702b316c 100644
--- a/src/components/EmojiPicker/CategoryShortcutButton.js
+++ b/src/components/EmojiPicker/CategoryShortcutButton.js
@@ -5,9 +5,9 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed
import Tooltip from '@components/Tooltip';
import useLocalize from '@hooks/useLocalize';
import getButtonState from '@libs/getButtonState';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
@@ -23,6 +23,8 @@ const propTypes = {
};
function CategoryShortcutButton(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const [isHighlighted, setIsHighlighted] = useState(false);
@@ -41,7 +43,7 @@ function CategoryShortcutButton(props) {
role={CONST.ACCESSIBILITY_ROLE.BUTTON}
>
{
+ const styles = useThemeStyles();
const [isEmojiPickerVisible, setIsEmojiPickerVisible] = useState(false);
const [emojiPopoverAnchorPosition, setEmojiPopoverAnchorPosition] = useState({
horizontal: 0,
@@ -134,6 +135,9 @@ const EmojiPicker = forwardRef((props, ref) => {
});
});
return () => {
+ if (!emojiPopoverDimensionListener) {
+ return;
+ }
emojiPopoverDimensionListener.remove();
};
}, [isEmojiPickerVisible, isSmallScreenWidth, emojiPopoverAnchorOrigin]);
diff --git a/src/components/EmojiPicker/EmojiPickerButton.js b/src/components/EmojiPicker/EmojiPickerButton.js
index ddfa6b89c899..db1834296a52 100644
--- a/src/components/EmojiPicker/EmojiPickerButton.js
+++ b/src/components/EmojiPicker/EmojiPickerButton.js
@@ -5,11 +5,9 @@ import * as Expensicons from '@components/Icon/Expensicons';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import Tooltip from '@components/Tooltip/PopoverAnchorTooltip';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import withNavigationFocus from '@components/withNavigationFocus';
-import compose from '@libs/compose';
import getButtonState from '@libs/getButtonState';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import * as EmojiPickerAction from '@userActions/EmojiPickerAction';
const propTypes = {
@@ -32,6 +30,7 @@ const defaultProps = {
};
function EmojiPickerButton(props) {
+ const styles = useThemeStyles();
const emojiPopoverAnchor = useRef(null);
useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []);
@@ -43,9 +42,6 @@ function EmojiPickerButton(props) {
style={({hovered, pressed}) => [styles.chatItemEmojiButton, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed))]}
disabled={props.isDisabled}
onPress={() => {
- if (!props.isFocused) {
- return;
- }
if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) {
EmojiPickerAction.showEmojiPicker(props.onModalHide, props.onEmojiSelected, emojiPopoverAnchor.current, undefined, () => {}, props.emojiPickerID);
} else {
@@ -69,4 +65,4 @@ function EmojiPickerButton(props) {
EmojiPickerButton.propTypes = propTypes;
EmojiPickerButton.defaultProps = defaultProps;
EmojiPickerButton.displayName = 'EmojiPickerButton';
-export default compose(withLocalize, withNavigationFocus)(EmojiPickerButton);
+export default withLocalize(EmojiPickerButton);
diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js
index bf0527c67768..7b4f4066593c 100644
--- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js
+++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js
@@ -8,8 +8,8 @@ import Text from '@components/Text';
import Tooltip from '@components/Tooltip';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import getButtonState from '@libs/getButtonState';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import * as EmojiPickerAction from '@userActions/EmojiPickerAction';
import CONST from '@src/CONST';
@@ -25,6 +25,7 @@ const defaultProps = {
};
function EmojiPickerButtonDropdown(props) {
+ const styles = useThemeStyles();
const emojiPopoverAnchor = useRef(null);
useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []);
diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
index 8a4653dc307e..772c32ff4a88 100644
--- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
+++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
@@ -15,8 +15,8 @@ import useSingleExecution from '@hooks/useSingleExecution';
import useWindowDimensions from '@hooks/useWindowDimensions';
import compose from '@libs/compose';
import * as EmojiUtils from '@libs/EmojiUtils';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -42,6 +42,7 @@ const defaultProps = {
};
function EmojiPickerMenu({preferredLocale, onEmojiSelected, preferredSkinTone, translate, frequentlyUsedEmojis}) {
+ const styles = useThemeStyles();
const emojiList = useAnimatedRef();
// eslint-disable-next-line react-hooks/exhaustive-deps
const allEmojis = useMemo(() => EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis), [frequentlyUsedEmojis]);
diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js
index 451e2e939a09..24f876841ff7 100644
--- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js
+++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js
@@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
import React, {PureComponent} from 'react';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import Text from '@components/Text';
+import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles';
import * as Browser from '@libs/Browser';
import getButtonState from '@libs/getButtonState';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
import CONST from '@src/CONST';
@@ -32,6 +32,7 @@ const propTypes = {
/** Whether the menu item should be highlighted or not */
isHighlighted: PropTypes.bool,
+ ...withThemeStylesPropTypes,
};
class EmojiPickerMenuItem extends PureComponent {
@@ -94,15 +95,15 @@ class EmojiPickerMenuItem extends PureComponent {
onBlur={this.props.onBlur}
ref={(ref) => (this.ref = ref)}
style={({pressed}) => [
- this.props.isFocused ? styles.emojiItemKeyboardHighlighted : {},
- this.state.isHovered || this.props.isHighlighted ? styles.emojiItemHighlighted : {},
+ this.props.isFocused ? this.props.themeStyles.emojiItemKeyboardHighlighted : {},
+ this.state.isHovered || this.props.isHighlighted ? this.props.themeStyles.emojiItemHighlighted : {},
Browser.isMobile() && StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)),
- styles.emojiItem,
+ this.props.themeStyles.emojiItem,
]}
accessibilityLabel={this.props.emoji}
role={CONST.ACCESSIBILITY_ROLE.BUTTON}
>
- {this.props.emoji}
+ {this.props.emoji}
);
}
@@ -120,7 +121,9 @@ EmojiPickerMenuItem.defaultProps = {
// Significantly speeds up re-renders of the EmojiPickerMenu's FlatList
// by only re-rendering at most two EmojiPickerMenuItems that are highlighted/un-highlighted per user action.
-export default React.memo(
- EmojiPickerMenuItem,
- (prevProps, nextProps) => prevProps.isFocused === nextProps.isFocused && prevProps.isHighlighted === nextProps.isHighlighted && prevProps.emoji === nextProps.emoji,
+export default withThemeStyles(
+ React.memo(
+ EmojiPickerMenuItem,
+ (prevProps, nextProps) => prevProps.isFocused === nextProps.isFocused && prevProps.isHighlighted === nextProps.isHighlighted && prevProps.emoji === nextProps.emoji,
+ ),
);
diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js
index 6ebaa3391992..151fabf85be3 100644
--- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js
+++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js
@@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
import React, {PureComponent} from 'react';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import Text from '@components/Text';
+import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles';
import getButtonState from '@libs/getButtonState';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
import CONST from '@src/CONST';
@@ -34,6 +34,7 @@ const propTypes = {
/** Whether the emoji is highlighted by the keyboard/mouse */
isUsingKeyboardMovement: PropTypes.bool,
+ ...withThemeStylesPropTypes,
};
class EmojiPickerMenuItem extends PureComponent {
@@ -72,14 +73,14 @@ class EmojiPickerMenuItem extends PureComponent {
ref={(ref) => (this.ref = ref)}
style={({pressed}) => [
StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)),
- this.props.isHighlighted && this.props.isUsingKeyboardMovement ? styles.emojiItemKeyboardHighlighted : {},
- this.props.isHighlighted && !this.props.isUsingKeyboardMovement ? styles.emojiItemHighlighted : {},
- styles.emojiItem,
+ this.props.isHighlighted && this.props.isUsingKeyboardMovement ? this.props.themeStyles.emojiItemKeyboardHighlighted : {},
+ this.props.isHighlighted && !this.props.isUsingKeyboardMovement ? this.props.themeStyles.emojiItemHighlighted : {},
+ this.props.themeStyles.emojiItem,
]}
accessibilityLabel={this.props.emoji}
role={CONST.ACCESSIBILITY_ROLE.BUTTON}
>
- {this.props.emoji}
+ {this.props.emoji}
);
}
@@ -98,8 +99,10 @@ EmojiPickerMenuItem.defaultProps = {
// Significantly speeds up re-renders of the EmojiPickerMenu's FlatList
// by only re-rendering at most two EmojiPickerMenuItems that are highlighted/un-highlighted per user action.
-export default React.memo(
- EmojiPickerMenuItem,
- (prevProps, nextProps) =>
- prevProps.isHighlighted === nextProps.isHighlighted && prevProps.emoji === nextProps.emoji && prevProps.isUsingKeyboardMovement === nextProps.isUsingKeyboardMovement,
+export default withThemeStyles(
+ React.memo(
+ EmojiPickerMenuItem,
+ (prevProps, nextProps) =>
+ prevProps.isHighlighted === nextProps.isHighlighted && prevProps.emoji === nextProps.emoji && prevProps.isUsingKeyboardMovement === nextProps.isUsingKeyboardMovement,
+ ),
);
diff --git a/src/components/EmojiPicker/EmojiSkinToneList.js b/src/components/EmojiPicker/EmojiSkinToneList.js
index 29c39c335b14..25fc9ad0836a 100644
--- a/src/components/EmojiPicker/EmojiSkinToneList.js
+++ b/src/components/EmojiPicker/EmojiSkinToneList.js
@@ -6,7 +6,7 @@ import * as Emojis from '@assets/emojis';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import EmojiPickerMenuItem from './EmojiPickerMenuItem';
import getSkinToneEmojiFromIndex from './getSkinToneEmojiFromIndex';
@@ -23,6 +23,7 @@ const propTypes = {
};
function EmojiSkinToneList(props) {
+ const styles = useThemeStyles();
const [highlightedIndex, setHighlightedIndex] = useState(null);
const [isSkinToneListVisible, setIsSkinToneListVisible] = useState(false);
diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx
index 8ab20cf13ad6..bb983a680af8 100644
--- a/src/components/EmojiSuggestions.tsx
+++ b/src/components/EmojiSuggestions.tsx
@@ -3,8 +3,8 @@ import {View} from 'react-native';
import type {SimpleEmoji} from '@libs/EmojiTrie';
import * as EmojiUtils from '@libs/EmojiUtils';
import getStyledTextArray from '@libs/GetStyledTextArray';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import AutoCompleteSuggestions from './AutoCompleteSuggestions';
import Text from './Text';
@@ -42,6 +42,7 @@ type EmojiSuggestionsProps = {
const keyExtractor = (item: SimpleEmoji, index: number): string => `${item.name}+${index}}`;
function EmojiSuggestions({emojis, onSelect, prefix, isEmojiPickerLarge, preferredSkinToneIndex, highlightedEmojiIndex = 0, measureParentContainer = () => {}}: EmojiSuggestionsProps) {
+ const styles = useThemeStyles();
/**
* Render an emoji suggestion menu item component.
*/
diff --git a/src/components/EnvironmentBadge.js b/src/components/EnvironmentBadge.js
index 674eaa5c2840..f32946f8bc25 100644
--- a/src/components/EnvironmentBadge.js
+++ b/src/components/EnvironmentBadge.js
@@ -1,7 +1,7 @@
import React from 'react';
import useEnvironment from '@hooks/useEnvironment';
import * as Environment from '@libs/Environment/Environment';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import pkg from '../../package.json';
import Badge from './Badge';
@@ -14,6 +14,7 @@ const ENVIRONMENT_SHORT_FORM = {
};
function EnvironmentBadge() {
+ const styles = useThemeStyles();
const {environment} = useEnvironment();
// If we are on production, don't show any badge
diff --git a/src/components/ExceededCommentLength.js b/src/components/ExceededCommentLength.js
index 43589be566ff..6353bdf40283 100644
--- a/src/components/ExceededCommentLength.js
+++ b/src/components/ExceededCommentLength.js
@@ -4,7 +4,7 @@ import React, {useEffect, useMemo, useState} from 'react';
import {withOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import Text from './Text';
@@ -26,6 +26,7 @@ const defaultProps = {
};
function ExceededCommentLength(props) {
+ const styles = useThemeStyles();
const {numberFormat, translate} = useLocalize();
const [commentLength, setCommentLength] = useState(0);
const updateCommentLength = useMemo(
diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js
index 1fe6aee91e67..efb3b20dbe87 100644
--- a/src/components/ExpensifyWordmark.js
+++ b/src/components/ExpensifyWordmark.js
@@ -7,9 +7,9 @@ import DevLogo from '@assets/images/expensify-logo--dev.svg';
import StagingLogo from '@assets/images/expensify-logo--staging.svg';
import ProductionLogo from '@assets/images/expensify-wordmark.svg';
import useEnvironment from '@hooks/useEnvironment';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
@@ -32,6 +32,8 @@ const logoComponents = {
};
function ExpensifyWordmark(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {environment} = useEnvironment();
// PascalCase is required for React components, so capitalize the const here
@@ -46,7 +48,7 @@ function ExpensifyWordmark(props) {
...(_.isArray(props.style) ? props.style : [props.style]),
]}
>
-
+
>
);
diff --git a/src/components/FeatureList.js b/src/components/FeatureList.js
index 94bb309edc84..85195864cdc3 100644
--- a/src/components/FeatureList.js
+++ b/src/components/FeatureList.js
@@ -3,7 +3,7 @@ import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import useLocalize from '@hooks/useLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import MenuItem from './MenuItem';
import menuItemPropTypes from './menuItemPropTypes';
import Text from './Text';
@@ -20,6 +20,7 @@ const propTypes = {
};
function FeatureList({menuItems, headline, description}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
return (
<>
diff --git a/src/components/FixedFooter.tsx b/src/components/FixedFooter.tsx
index afda41f16d06..34bce2133a89 100644
--- a/src/components/FixedFooter.tsx
+++ b/src/components/FixedFooter.tsx
@@ -1,6 +1,6 @@
import React, {ReactNode} from 'react';
import {StyleProp, View, ViewStyle} from 'react-native';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
type FixedFooterProps = {
/** Children to wrap in FixedFooter. */
@@ -11,6 +11,7 @@ type FixedFooterProps = {
};
function FixedFooter({style = [], children}: FixedFooterProps) {
+ const styles = useThemeStyles();
return {children};
}
diff --git a/src/components/FloatingActionButton.js b/src/components/FloatingActionButton.js
index c0e01cab2954..c49f69c336eb 100644
--- a/src/components/FloatingActionButton.js
+++ b/src/components/FloatingActionButton.js
@@ -1,14 +1,15 @@
import PropTypes from 'prop-types';
import React, {PureComponent} from 'react';
import {Animated, Easing, View} from 'react-native';
-import styles from '@styles/styles';
+import compose from '@libs/compose';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import PressableWithFeedback from './Pressable/PressableWithFeedback';
import Tooltip from './Tooltip/PopoverAnchorTooltip';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
+import withTheme, {withThemePropTypes} from './withTheme';
+import withThemeStyles, {withThemeStylesPropTypes} from './withThemeStyles';
const AnimatedIcon = Animated.createAnimatedComponent(Icon);
AnimatedIcon.displayName = 'AnimatedIcon';
@@ -27,6 +28,8 @@ const propTypes = {
buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
...withLocalizePropTypes,
+ ...withThemeStylesPropTypes,
+ ...withThemePropTypes,
};
const defaultProps = {
@@ -70,17 +73,17 @@ class FloatingActionButton extends PureComponent {
const backgroundColor = this.animatedValue.interpolate({
inputRange: [0, 1],
- outputRange: [themeColors.success, themeColors.buttonDefaultBG],
+ outputRange: [this.props.theme.success, this.props.theme.buttonDefaultBG],
});
const fill = this.animatedValue.interpolate({
inputRange: [0, 1],
- outputRange: [themeColors.textLight, themeColors.textDark],
+ outputRange: [this.props.theme.textLight, this.props.theme.textDark],
});
return (
-
+
{
this.fabPressable = el;
@@ -97,7 +100,7 @@ class FloatingActionButton extends PureComponent {
this.props.onPress(e);
}}
onLongPress={() => {}}
- style={[styles.floatingActionButton, StyleUtils.getAnimatedFABStyle(rotate, backgroundColor)]}
+ style={[this.props.themeStyles.floatingActionButton, StyleUtils.getAnimatedFABStyle(rotate, backgroundColor)]}
>
FloatingActionButtonWithLocalizeWithRef.displayName = 'FloatingActionButtonWithLocalizeWithRef';
-export default FloatingActionButtonWithLocalizeWithRef;
+export default compose(withThemeStyles, withTheme)(FloatingActionButtonWithLocalizeWithRef);
diff --git a/src/components/Form.js b/src/components/Form.js
index 372c7a0c5d9b..28343691ea15 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -9,7 +9,7 @@ import * as ErrorUtils from '@libs/ErrorUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
import Visibility from '@libs/Visibility';
import stylePropTypes from '@styles/stylePropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as FormActions from '@userActions/FormActions';
import CONST from '@src/CONST';
import FormAlertWithSubmitButton from './FormAlertWithSubmitButton';
@@ -108,6 +108,7 @@ const defaultProps = {
};
function Form(props) {
+ const styles = useThemeStyles();
const [errors, setErrors] = useState({});
const [inputValues, setInputValues] = useState(() => ({...props.draftValues}));
const formRef = useRef(null);
@@ -458,24 +459,26 @@ function Form(props) {
)}
),
+
[
- childrenWrapperWithProps,
- errors,
- formContentRef,
- formRef,
- errorMessage,
- inputRefs,
- inputValues,
- submit,
props.style,
- children,
- props.formState,
- props.footerContent,
- props.enabledWhenOffline,
- props.isSubmitActionDangerous,
props.isSubmitButtonVisible,
props.submitButtonText,
+ props.formState.errorFields,
+ props.formState.isLoading,
+ props.footerContent,
props.submitButtonStyles,
+ props.enabledWhenOffline,
+ props.isSubmitActionDangerous,
+ submit,
+ childrenWrapperWithProps,
+ children,
+ inputValues,
+ errors,
+ errorMessage,
+ styles.mh0,
+ styles.mt5,
+ styles.flex1,
],
);
diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js
index fa0cc3ebd723..92c76da5936d 100644
--- a/src/components/Form/FormProvider.js
+++ b/src/components/Form/FormProvider.js
@@ -317,7 +317,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC
errors={errors}
enabledWhenOffline={enabledWhenOffline}
>
- {children}
+ {_.isFunction(children) ? children({inputValues}) : children}
);
diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js
index f82199d0f587..4f7346a94a2d 100644
--- a/src/components/Form/FormWrapper.js
+++ b/src/components/Form/FormWrapper.js
@@ -9,7 +9,7 @@ import SafeAreaConsumer from '@components/SafeAreaConsumer';
import ScrollViewWithContext from '@components/ScrollViewWithContext';
import * as ErrorUtils from '@libs/ErrorUtils';
import stylePropTypes from '@styles/stylePropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import errorsPropType from './errorsPropType';
const propTypes = {
@@ -81,6 +81,7 @@ const defaultProps = {
};
function FormWrapper(props) {
+ const styles = useThemeStyles();
const {
onSubmit,
children,
@@ -169,6 +170,9 @@ function FormWrapper(props) {
isSubmitButtonVisible,
onSubmit,
style,
+ styles.flex1,
+ styles.mh0,
+ styles.mt5,
submitButtonStyles,
submitButtonText,
],
diff --git a/src/components/FormAlertWithSubmitButton.js b/src/components/FormAlertWithSubmitButton.js
index 1ffbf0d667e2..b16a4d2a08ee 100644
--- a/src/components/FormAlertWithSubmitButton.js
+++ b/src/components/FormAlertWithSubmitButton.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import Button from './Button';
import FormAlertWrapper from './FormAlertWrapper';
@@ -67,6 +67,7 @@ const defaultProps = {
};
function FormAlertWithSubmitButton(props) {
+ const styles = useThemeStyles();
const buttonStyles = [_.isEmpty(props.footerContent) ? {} : styles.mb3, ...props.buttonStyles];
return (
diff --git a/src/components/FormAlertWrapper.js b/src/components/FormAlertWrapper.js
index 87304ee5dc3e..c577048c0a1b 100644
--- a/src/components/FormAlertWrapper.js
+++ b/src/components/FormAlertWrapper.js
@@ -3,7 +3,7 @@ import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import compose from '@libs/compose';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import FormHelpMessage from './FormHelpMessage';
import networkPropTypes from './networkPropTypes';
import {withNetwork} from './OnyxProvider';
@@ -51,6 +51,7 @@ const defaultProps = {
// This component takes other components as a child prop. It will then render any wrapped components as a function using "render props",
// and passes it a (bool) isOffline parameter. Child components can then use the isOffline variable to determine offline behavior.
function FormAlertWrapper(props) {
+ const styles = useThemeStyles();
let children;
if (_.isEmpty(props.message)) {
children = (
diff --git a/src/components/FormElement.js b/src/components/FormElement.js
deleted file mode 100644
index d929ddb5f2e4..000000000000
--- a/src/components/FormElement.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React, {forwardRef} from 'react';
-import {View} from 'react-native';
-import * as ComponentUtils from '@libs/ComponentUtils';
-
-const FormElement = forwardRef((props, ref) => (
-
-));
-
-FormElement.displayName = 'BaseForm';
-export default FormElement;
diff --git a/src/components/FormElement.tsx b/src/components/FormElement.tsx
new file mode 100644
index 000000000000..c61a09b9d1ec
--- /dev/null
+++ b/src/components/FormElement.tsx
@@ -0,0 +1,18 @@
+import React, {ForwardedRef, forwardRef} from 'react';
+import {View, ViewProps} from 'react-native';
+import * as ComponentUtils from '@libs/ComponentUtils';
+
+function FormElement(props: ViewProps, ref: ForwardedRef) {
+ return (
+
+ );
+}
+
+FormElement.displayName = 'FormElement';
+
+export default forwardRef(FormElement);
diff --git a/src/components/FormHelpMessage.js b/src/components/FormHelpMessage.js
index 717b9d9355b6..bec02c3d51f0 100644
--- a/src/components/FormHelpMessage.js
+++ b/src/components/FormHelpMessage.js
@@ -4,8 +4,8 @@ import {View} from 'react-native';
import _ from 'underscore';
import * as Localize from '@libs/Localize';
import stylePropTypes from '@styles/stylePropTypes';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';
@@ -32,6 +32,8 @@ const defaultProps = {
};
function FormHelpMessage(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
if (_.isEmpty(props.message) && _.isEmpty(props.children)) {
return null;
}
@@ -42,9 +44,10 @@ function FormHelpMessage(props) {
{props.isError && (
)}
+
{props.children || {translatedMessage}}
diff --git a/src/components/FormScrollView.tsx b/src/components/FormScrollView.tsx
index e47d75f4fb26..c2f01e1d0511 100644
--- a/src/components/FormScrollView.tsx
+++ b/src/components/FormScrollView.tsx
@@ -1,6 +1,6 @@
import React, {ForwardedRef} from 'react';
import {ScrollView, ScrollViewProps} from 'react-native';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
type FormScrollViewProps = ScrollViewProps & {
/** Form elements */
@@ -8,6 +8,7 @@ type FormScrollViewProps = ScrollViewProps & {
};
function FormScrollView({children, ...rest}: FormScrollViewProps, ref: ForwardedRef) {
+ const styles = useThemeStyles();
return (
;
};
function FullScreenLoadingIndicator({style}: FullScreenLoadingIndicatorProps) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
return (
diff --git a/src/components/GrowlNotification/GrowlNotificationContainer/index.js b/src/components/GrowlNotification/GrowlNotificationContainer/index.js
index c6614e371b6d..82672edb14c2 100644
--- a/src/components/GrowlNotification/GrowlNotificationContainer/index.js
+++ b/src/components/GrowlNotification/GrowlNotificationContainer/index.js
@@ -1,7 +1,7 @@
import React from 'react';
import {Animated} from 'react-native';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import growlNotificationContainerPropTypes from './growlNotificationContainerPropTypes';
const propTypes = {
@@ -10,6 +10,7 @@ const propTypes = {
};
function GrowlNotificationContainer(props) {
+ const styles = useThemeStyles();
return (
{(insets) => (
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
index 52cbc88a6652..63973ea43e19 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
@@ -9,7 +9,7 @@ import useEnvironment from '@hooks/useEnvironment';
import Navigation from '@libs/Navigation/Navigation';
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
import * as Url from '@libs/Url';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Link from '@userActions/Link';
import * as Session from '@userActions/Session';
import CONFIG from '@src/CONFIG';
@@ -18,6 +18,7 @@ import ROUTES from '@src/ROUTES';
import htmlRendererPropTypes from './htmlRendererPropTypes';
function AnchorRenderer(props) {
+ const styles = useThemeStyles();
const htmlAttribs = props.tnode.attributes;
const {environmentURL} = useEnvironment();
// An auth token is needed to download Expensify chat attachments
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js
index 03d3afe53d4e..e97d01808a6e 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js
@@ -3,8 +3,8 @@ import _ from 'underscore';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import editedLabelStyles from '@styles/editedLabelStyles';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import htmlRendererPropTypes from './htmlRendererPropTypes';
@@ -15,6 +15,8 @@ const propTypes = {
};
function EditedRenderer(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']);
const isPendingDelete = Boolean(props.tnode.attributes.deleted !== undefined);
return (
@@ -29,7 +31,7 @@ function EditedRenderer(props) {
// eslint-disable-next-line react/jsx-props-no-spreading
{...defaultRendererProps}
fontSize={variables.fontSizeSmall}
- color={themeColors.textSupporting}
+ color={theme.textSupporting}
style={[editedLabelStyles, isPendingDelete && styles.offlineFeedback.deleted]}
>
{props.translate('reportActionCompose.edited')}
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
index 8461f714373b..7cbdf8d69831 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
@@ -8,7 +8,7 @@ import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -17,6 +17,7 @@ import htmlRendererPropTypes from './htmlRendererPropTypes';
const propTypes = {...htmlRendererPropTypes};
function ImageRenderer(props) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const htmlAttribs = props.tnode.attributes;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js
index b7b7c43e7b58..a60be614d9bc 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.js
@@ -14,8 +14,8 @@ import Navigation from '@libs/Navigation/Navigation';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import personalDetailsPropType from '@pages/personalDetailsPropType';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -32,6 +32,7 @@ const propTypes = {
};
function MentionUserRenderer(props) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style']);
const htmlAttribAccountID = lodashGet(props.tnode.attributes, 'accountid');
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js
index 8edc7baeac65..a2b01059a63c 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js
@@ -7,7 +7,7 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed
import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext';
import withLocalize from '@components/withLocalize';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -32,6 +32,7 @@ const defaultProps = {
};
const BasePreRenderer = forwardRef((props, ref) => {
+ const styles = useThemeStyles();
const TDefaultRenderer = props.TDefaultRenderer;
const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'onPressIn', 'onPressOut', 'onLongPress']);
const isLast = props.renderIndex === props.renderLength - 1;
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index eb9a9b2c55c0..46fe1a25c920 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -1,6 +1,6 @@
import React, {ReactElement} from 'react';
import {StyleProp, TextStyle, View} from 'react-native';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import EnvironmentBadge from './EnvironmentBadge';
import Text from './Text';
@@ -19,6 +19,7 @@ type HeaderProps = {
};
function Header({title = '', subtitle = '', textStyles = [], shouldShowEnvironmentBadge = false}: HeaderProps) {
+ const styles = useThemeStyles();
return (
diff --git a/src/components/HeaderGap/index.desktop.js b/src/components/HeaderGap/index.desktop.js
index 02918fbe3a51..2d583881cab6 100644
--- a/src/components/HeaderGap/index.desktop.js
+++ b/src/components/HeaderGap/index.desktop.js
@@ -1,17 +1,18 @@
import PropTypes from 'prop-types';
import React, {PureComponent} from 'react';
import {View} from 'react-native';
-import styles from '@styles/styles';
+import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles';
const propTypes = {
/** Styles to apply to the HeaderGap */
// eslint-disable-next-line react/forbid-prop-types
styles: PropTypes.arrayOf(PropTypes.object),
+ ...withThemeStylesPropTypes,
};
class HeaderGap extends PureComponent {
render() {
- return ;
+ return ;
}
}
@@ -19,4 +20,4 @@ HeaderGap.propTypes = propTypes;
HeaderGap.defaultProps = {
styles: [],
};
-export default HeaderGap;
+export default withThemeStyles(HeaderGap);
diff --git a/src/components/HeaderPageLayout.js b/src/components/HeaderPageLayout.js
index daacf197a672..260f98d208eb 100644
--- a/src/components/HeaderPageLayout.js
+++ b/src/components/HeaderPageLayout.js
@@ -5,9 +5,9 @@ import _ from 'underscore';
import useNetwork from '@hooks/useNetwork';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import FixedFooter from './FixedFooter';
import HeaderWithBackButton from './HeaderWithBackButton';
import headerWithBackButtonPropTypes from './HeaderWithBackButton/headerWithBackButtonPropTypes';
@@ -42,7 +42,7 @@ const propTypes = {
};
const defaultProps = {
- backgroundColor: themeColors.appBG,
+ backgroundColor: undefined,
header: null,
headerContainerStyles: [],
scrollViewContainerStyles: [],
@@ -51,20 +51,22 @@ const defaultProps = {
};
function HeaderPageLayout({backgroundColor, children, footer, headerContainerStyles, scrollViewContainerStyles, childrenContainerStyles, style, headerContent, ...propsToPassToHeader}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {windowHeight, isSmallScreenWidth} = useWindowDimensions();
const {isOffline} = useNetwork();
- const appBGColor = StyleUtils.getBackgroundColorStyle(themeColors.appBG);
+ const appBGColor = StyleUtils.getBackgroundColorStyle(theme.appBG);
const {titleColor, iconFill} = useMemo(() => {
- const isColorfulBackground = backgroundColor !== themeColors.appBG;
+ const isColorfulBackground = (backgroundColor || theme.appBG) !== theme.appBG;
return {
- titleColor: isColorfulBackground ? themeColors.textColorfulBackground : undefined,
- iconFill: isColorfulBackground ? themeColors.iconColorfulBackground : undefined,
+ titleColor: isColorfulBackground ? theme.textColorfulBackground : undefined,
+ iconFill: isColorfulBackground ? theme.iconColorfulBackground : undefined,
};
- }, [backgroundColor]);
+ }, [backgroundColor, theme.appBG, theme.iconColorfulBackground, theme.textColorfulBackground]);
return (
-
+
)}
@@ -90,8 +92,8 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty
contentContainerStyle={[safeAreaPaddingBottomStyle, style, scrollViewContainerStyles]}
offlineIndicatorStyle={[appBGColor]}
>
- {!Browser.isSafari() && }
-
+ {!Browser.isSafari() && }
+
{headerContent}
{children}
diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js
index 6a8f630d1e78..1371e6a36b97 100755
--- a/src/components/HeaderWithBackButton/index.js
+++ b/src/components/HeaderWithBackButton/index.js
@@ -14,8 +14,8 @@ import useThrottledButtonState from '@hooks/useThrottledButtonState';
import useWaitForNavigation from '@hooks/useWaitForNavigation';
import getButtonState from '@libs/getButtonState';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import headerWithBackButtonPropTypes from './headerWithBackButtonPropTypes';
@@ -54,6 +54,7 @@ function HeaderWithBackButton({
shouldOverlay = false,
singleExecution = (func) => func,
}) {
+ const styles = useThemeStyles();
const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState();
const {translate} = useLocalize();
const {isKeyboardShown} = useKeyboardState();
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index a4aa6b13cb29..3d4f0edb1656 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -45,6 +45,7 @@ import Download from '@assets/images/download.svg';
import DragAndDrop from '@assets/images/drag-and-drop.svg';
import DragHandles from '@assets/images/drag-handles.svg';
import Emoji from '@assets/images/emoji.svg';
+import EmptyStateAttachReceipt from '@assets/images/empty-state__attach-receipt.svg';
import EmptyStateRoutePending from '@assets/images/emptystate__routepending.svg';
import EReceiptIcon from '@assets/images/eReceiptIcon.svg';
import Exclamation from '@assets/images/exclamation.svg';
@@ -176,6 +177,7 @@ export {
EReceiptIcon,
Emoji,
EmptyStateRoutePending,
+ EmptyStateAttachReceipt,
Exclamation,
Exit,
ExpensifyCard,
diff --git a/src/components/Icon/svgs/LoungeAccessIcon.tsx b/src/components/Icon/svgs/LoungeAccessIcon.tsx
index fffbe1b60451..48b140da3bc8 100644
--- a/src/components/Icon/svgs/LoungeAccessIcon.tsx
+++ b/src/components/Icon/svgs/LoungeAccessIcon.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import Svg, {G, Path, Polygon} from 'react-native-svg';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
type LoungeAccessIconProps = {
/** The fill color for the icon. Can be hex, rgb, rgba, or valid react-native named color such as 'red' or 'blue'. */
@@ -19,7 +19,8 @@ type LoungeAccessIconProps = {
height?: number;
};
-function LoungeAccessIcon({fill = themeColors.icon, hovered = 'false', pressed = 'false', width, height}: LoungeAccessIconProps) {
+function LoungeAccessIcon({fill, hovered = 'false', pressed = 'false', width, height}: LoungeAccessIconProps) {
+ const theme = useTheme();
return (
diff --git a/src/components/WalletSection.js b/src/components/WalletSection.js
index d79d2be0ddc9..1b5349098b82 100644
--- a/src/components/WalletSection.js
+++ b/src/components/WalletSection.js
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import Section from './Section';
const propTypes = {
@@ -24,6 +24,7 @@ const defaultProps = {
};
function WalletSection({children, icon, subtitle, title}) {
+ const styles = useThemeStyles();
return (
(
-
- {(translateUtils) => (
-
- )}
-
- ));
+type WithLocalizeProps = LocaleContextProps;
+
+export default function withLocalize(
+ WrappedComponent: ComponentType>,
+): (props: Omit & React.RefAttributes) => ReactElement | null {
+ function WithLocalize(props: Omit, ref: ForwardedRef) {
+ return (
+
+ {(translateUtils) => (
+
+ )}
+
+ );
+ }
WithLocalize.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`;
-
- return WithLocalize;
+ return forwardRef(WithLocalize);
}
export {withLocalizePropTypes};
+export type {WithLocalizeProps};
diff --git a/src/components/withTabAnimation.js b/src/components/withTabAnimation.js
deleted file mode 100644
index 2af96f0215a3..000000000000
--- a/src/components/withTabAnimation.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import {useTabAnimation} from '@react-navigation/material-top-tabs';
-import PropTypes from 'prop-types';
-import * as React from 'react';
-import getComponentDisplayName from '@libs/getComponentDisplayName';
-import refPropTypes from './refPropTypes';
-
-const propTypes = {
- /** The HOC takes an optional ref as a prop and passes it as a ref to the wrapped component.
- * That way, if a ref is passed to a component wrapped in the HOC, the ref is a reference to the wrapped component, not the HOC. */
- forwardedRef: refPropTypes,
-
- /* Whether we're in a tab navigator */
- isInTabNavigator: PropTypes.bool.isRequired,
-};
-
-const defaultProps = {
- forwardedRef: () => {},
-};
-
-export default function (WrappedComponent) {
- // The component with tab animation prop
- function WrappedComponentWithTabAnimation(props) {
- const animation = useTabAnimation();
-
- return (
-
- );
- }
-
- WrappedComponentWithTabAnimation.displayName = `withAnimation(${getComponentDisplayName(WrappedComponent)})`;
-
- // Return a component with tab animation prop if this component is in tab navigator, otherwise return itself
- function WithTabAnimation({forwardedRef, ...rest}) {
- if (rest.isInTabNavigator) {
- return (
-
- );
- }
- return (
-
- );
- }
-
- WithTabAnimation.propTypes = propTypes;
- WithTabAnimation.defaultProps = defaultProps;
- WithTabAnimation.displayName = `withTabAnimation(${getComponentDisplayName(WrappedComponent)})`;
-
- // eslint-disable-next-line rulesdir/no-negated-variables
- const WithTabAnimationWithRef = React.forwardRef((props, ref) => (
-
- ));
-
- WithTabAnimationWithRef.displayName = `withTabAnimationWithRef(${getComponentDisplayName(WrappedComponent)})`;
-
- return WithTabAnimationWithRef;
-}
diff --git a/src/components/withTheme.js b/src/components/withTheme.js
deleted file mode 100644
index 1d8af53de01d..000000000000
--- a/src/components/withTheme.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import getComponentDisplayName from '@libs/getComponentDisplayName';
-import useTheme from '@styles/themes/useTheme';
-import refPropTypes from './refPropTypes';
-
-const withThemePropTypes = {
- theme: PropTypes.object.isRequired,
-};
-
-export default function withTheme(WrappedComponent) {
- function WithTheme(props) {
- const theme = useTheme();
- return (
-
- );
- }
-
- WithTheme.displayName = `withTheme(${getComponentDisplayName(WrappedComponent)})`;
- WithTheme.propTypes = {
- forwardedRef: refPropTypes,
- };
- WithTheme.defaultProps = {
- forwardedRef: () => {},
- };
-
- const WithThemeWithRef = React.forwardRef((props, ref) => (
-
- ));
-
- WithThemeWithRef.displayName = `WithThemeWithRef`;
-
- return WithThemeWithRef;
-}
-
-export {withThemePropTypes};
diff --git a/src/components/withTheme.tsx b/src/components/withTheme.tsx
new file mode 100644
index 000000000000..d78742b7036b
--- /dev/null
+++ b/src/components/withTheme.tsx
@@ -0,0 +1,32 @@
+import PropTypes from 'prop-types';
+import React, {ComponentType, ForwardedRef, forwardRef, ReactElement, RefAttributes} from 'react';
+import getComponentDisplayName from '@libs/getComponentDisplayName';
+import {ThemeColors} from '@styles/themes/types';
+import useTheme from '@styles/themes/useTheme';
+
+const withThemePropTypes = {
+ theme: PropTypes.object.isRequired,
+};
+type ThemeProps = {theme: ThemeColors};
+
+export default function withTheme(
+ WrappedComponent: ComponentType>,
+): (props: Omit & React.RefAttributes) => ReactElement | null {
+ function WithTheme(props: Omit, ref: ForwardedRef): ReactElement {
+ const theme = useTheme();
+ return (
+
+ );
+ }
+
+ WithTheme.displayName = `withTheme(${getComponentDisplayName(WrappedComponent)})`;
+
+ return forwardRef(WithTheme);
+}
+
+export {withThemePropTypes};
diff --git a/src/components/withThemeStyles.js b/src/components/withThemeStyles.js
deleted file mode 100644
index 533efa79a580..000000000000
--- a/src/components/withThemeStyles.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import getComponentDisplayName from '@libs/getComponentDisplayName';
-import useThemeStyles from '@styles/useThemeStyles';
-import refPropTypes from './refPropTypes';
-
-const withThemeStylesPropTypes = {
- themeStyles: PropTypes.object.isRequired,
-};
-
-export default function withThemeStyles(WrappedComponent) {
- function WithThemeStyles(props) {
- const themeStyles = useThemeStyles();
- return (
-
- );
- }
-
- WithThemeStyles.displayName = `withThemeStyles(${getComponentDisplayName(WrappedComponent)})`;
- WithThemeStyles.propTypes = {
- forwardedRef: refPropTypes,
- };
- WithThemeStyles.defaultProps = {
- forwardedRef: () => {},
- };
-
- const WithThemeStylesWithRef = React.forwardRef((props, ref) => (
-
- ));
-
- WithThemeStylesWithRef.displayName = `WithThemeStylesWithRef`;
-
- return WithThemeStylesWithRef;
-}
-
-export {withThemeStylesPropTypes};
diff --git a/src/components/withThemeStyles.tsx b/src/components/withThemeStyles.tsx
new file mode 100644
index 000000000000..d95122c3e2ba
--- /dev/null
+++ b/src/components/withThemeStyles.tsx
@@ -0,0 +1,32 @@
+import PropTypes from 'prop-types';
+import React, {ComponentType, ForwardedRef, forwardRef, ReactElement, RefAttributes} from 'react';
+import getComponentDisplayName from '@libs/getComponentDisplayName';
+import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
+
+const withThemeStylesPropTypes = {
+ themeStyles: PropTypes.object.isRequired,
+};
+type ThemeStylesProps = {themeStyles: typeof styles};
+
+export default function withThemeStyles(
+ WrappedComponent: ComponentType>,
+): (props: Omit & React.RefAttributes) => ReactElement | null {
+ function WithThemeStyles(props: Omit, ref: ForwardedRef): ReactElement {
+ const themeStyles = useThemeStyles();
+ return (
+
+ );
+ }
+
+ WithThemeStyles.displayName = `withThemeStyles(${getComponentDisplayName(WrappedComponent)})`;
+
+ return forwardRef(WithThemeStyles);
+}
+
+export {withThemeStylesPropTypes};
diff --git a/src/components/withToggleVisibilityView.tsx b/src/components/withToggleVisibilityView.tsx
index 5e0204f6e06f..0e3e91d09bf2 100644
--- a/src/components/withToggleVisibilityView.tsx
+++ b/src/components/withToggleVisibilityView.tsx
@@ -2,7 +2,7 @@ import React, {ComponentType, ForwardedRef, ReactElement, RefAttributes} from 'r
import {View} from 'react-native';
import {SetOptional} from 'type-fest';
import getComponentDisplayName from '@libs/getComponentDisplayName';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
type ToggleVisibilityViewProps = {
/** Whether the content is visible. */
@@ -13,6 +13,7 @@ export default function withToggleVisibilityView>,
): (props: TProps & RefAttributes) => ReactElement | null {
function WithToggleVisibilityView({isVisible = false, ...rest}: SetOptional, ref: ForwardedRef) {
+ const styles = useThemeStyles();
return (
(null);
const windowDimensionsPropTypes = {
// Width of the window
windowWidth: PropTypes.number.isRequired,
@@ -27,12 +29,7 @@ const windowDimensionsPropTypes = {
isLargeScreenWidth: PropTypes.bool.isRequired,
};
-const windowDimensionsProviderPropTypes = {
- /* Actual content wrapped by this component */
- children: PropTypes.node.isRequired,
-};
-
-function WindowDimensionsProvider(props) {
+function WindowDimensionsProvider(props: ChildrenProps) {
const [windowDimension, setWindowDimension] = useState(() => {
const initialDimensions = Dimensions.get('window');
return {
@@ -42,9 +39,8 @@ function WindowDimensionsProvider(props) {
});
useEffect(() => {
- const onDimensionChange = (newDimensions) => {
+ const onDimensionChange = (newDimensions: NewDimensions) => {
const {window} = newDimensions;
-
setWindowDimension({
windowHeight: window.height,
windowWidth: window.width,
@@ -76,30 +72,29 @@ function WindowDimensionsProvider(props) {
return {props.children};
}
-WindowDimensionsProvider.propTypes = windowDimensionsProviderPropTypes;
WindowDimensionsProvider.displayName = 'WindowDimensionsProvider';
-/**
- * @param {React.Component} WrappedComponent
- * @returns {React.Component}
- */
-export default function withWindowDimensions(WrappedComponent) {
- const WithWindowDimensions = forwardRef((props, ref) => (
-
- {(windowDimensionsProps) => (
-
- )}
-
- ));
+export default function withWindowDimensions(
+ WrappedComponent: ComponentType>,
+): (props: Omit & React.RefAttributes) => React.ReactElement | null {
+ function WithWindowDimensions(props: Omit, ref: ForwardedRef) {
+ return (
+
+ {(windowDimensionsProps) => (
+
+ )}
+
+ );
+ }
WithWindowDimensions.displayName = `withWindowDimensions(${getComponentDisplayName(WrappedComponent)})`;
- return WithWindowDimensions;
+ return React.forwardRef(WithWindowDimensions);
}
export {WindowDimensionsProvider, windowDimensionsPropTypes};
diff --git a/src/components/withWindowDimensions/index.js b/src/components/withWindowDimensions/index.tsx
similarity index 68%
rename from src/components/withWindowDimensions/index.js
rename to src/components/withWindowDimensions/index.tsx
index f46624b2f41c..1479450deec4 100644
--- a/src/components/withWindowDimensions/index.js
+++ b/src/components/withWindowDimensions/index.tsx
@@ -1,13 +1,15 @@
import lodashDebounce from 'lodash/debounce';
import PropTypes from 'prop-types';
-import React, {createContext, forwardRef, useEffect, useMemo, useState} from 'react';
+import React, {ComponentType, createContext, ForwardedRef, RefAttributes, useEffect, useMemo, useState} from 'react';
import {Dimensions} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import getComponentDisplayName from '@libs/getComponentDisplayName';
import getWindowHeightAdjustment from '@libs/getWindowHeightAdjustment';
import variables from '@styles/variables';
+import ChildrenProps from '@src/types/utils/ChildrenProps';
+import {NewDimensions, WindowDimensionsContextData, WindowDimensionsProps} from './types';
-const WindowDimensionsContext = createContext(null);
+const WindowDimensionsContext = createContext(null);
const windowDimensionsPropTypes = {
// Width of the window
windowWidth: PropTypes.number.isRequired,
@@ -28,12 +30,7 @@ const windowDimensionsPropTypes = {
isLargeScreenWidth: PropTypes.bool.isRequired,
};
-const windowDimensionsProviderPropTypes = {
- /* Actual content wrapped by this component */
- children: PropTypes.node.isRequired,
-};
-
-function WindowDimensionsProvider(props) {
+function WindowDimensionsProvider(props: ChildrenProps) {
const [windowDimension, setWindowDimension] = useState(() => {
const initialDimensions = Dimensions.get('window');
return {
@@ -43,7 +40,7 @@ function WindowDimensionsProvider(props) {
});
useEffect(() => {
- const onDimensionChange = (newDimensions) => {
+ const onDimensionChange = (newDimensions: NewDimensions) => {
const {window} = newDimensions;
setWindowDimension({
windowHeight: window.height,
@@ -81,30 +78,29 @@ function WindowDimensionsProvider(props) {
return {props.children};
}
-WindowDimensionsProvider.propTypes = windowDimensionsProviderPropTypes;
WindowDimensionsProvider.displayName = 'WindowDimensionsProvider';
-/**
- * @param {React.Component} WrappedComponent
- * @returns {React.Component}
- */
-export default function withWindowDimensions(WrappedComponent) {
- const WithWindowDimensions = forwardRef((props, ref) => (
-
- {(windowDimensionsProps) => (
-
- )}
-
- ));
+export default function withWindowDimensions(
+ WrappedComponent: ComponentType>,
+): (props: Omit & React.RefAttributes) => React.ReactElement | null {
+ function WithWindowDimensions(props: Omit, ref: ForwardedRef) {
+ return (
+
+ {(windowDimensionsProps) => (
+
+ )}
+
+ );
+ }
WithWindowDimensions.displayName = `withWindowDimensions(${getComponentDisplayName(WrappedComponent)})`;
- return WithWindowDimensions;
+ return React.forwardRef(WithWindowDimensions);
}
export {WindowDimensionsProvider, windowDimensionsPropTypes};
diff --git a/src/components/withWindowDimensions/types.ts b/src/components/withWindowDimensions/types.ts
new file mode 100644
index 000000000000..514c86616b87
--- /dev/null
+++ b/src/components/withWindowDimensions/types.ts
@@ -0,0 +1,34 @@
+import {ScaledSize} from 'react-native';
+
+type WindowDimensionsContextData = {
+ windowHeight: number;
+ windowWidth: number;
+ isExtraSmallScreenWidth: boolean;
+ isSmallScreenWidth: boolean;
+ isMediumScreenWidth: boolean;
+ isLargeScreenWidth: boolean;
+};
+
+type WindowDimensionsProps = WindowDimensionsContextData & {
+ // Width of the window
+ windowWidth: number;
+
+ // Height of the window
+ windowHeight: number;
+
+ // Is the window width extra narrow, like on a Fold mobile device?
+ isExtraSmallScreenWidth: boolean;
+
+ // Is the window width narrow, like on a mobile device?
+ isSmallScreenWidth: boolean;
+
+ // Is the window width medium sized, like on a tablet device?
+ isMediumScreenWidth: boolean;
+
+ // Is the window width wide, like on a browser or desktop?
+ isLargeScreenWidth: boolean;
+};
+
+type NewDimensions = {window: ScaledSize};
+
+export type {WindowDimensionsContextData, WindowDimensionsProps, NewDimensions};
diff --git a/src/hooks/useTabNavigatorFocus/index.js b/src/hooks/useTabNavigatorFocus/index.js
new file mode 100644
index 000000000000..f83ec5bd9270
--- /dev/null
+++ b/src/hooks/useTabNavigatorFocus/index.js
@@ -0,0 +1,79 @@
+import {useTabAnimation} from '@react-navigation/material-top-tabs';
+import {useIsFocused} from '@react-navigation/native';
+import {useEffect, useState} from 'react';
+import DomUtils from '@libs/DomUtils';
+
+/**
+ * Custom React hook to determine the focus status of a tab in a Material Top Tab Navigator.
+ * It evaluates whether the current tab is focused based on the tab's animation position and
+ * the screen's focus status within a React Navigation environment.
+ *
+ * This hook is designed for use with the Material Top Tabs provided by '@react-navigation/material-top-tabs'.
+ * It leverages the `useTabAnimation` hook from the same package to track the animated position of tabs
+ * and the `useIsFocused` hook from '@react-navigation/native' to ascertain if the current screen is in focus.
+ *
+ * Note: This hook contains a conditional invocation of another hook (`useTabAnimation`),
+ * which is typically an anti-pattern in React. This is done to account for scenarios where the hook
+ * might not be used within a Material Top Tabs Navigator context. Proper usage should ensure that
+ * this hook is only used where appropriate.
+ *
+ * @param {Object} params - The parameters object.
+ * @param {Number} params.tabIndex - The index of the tab for which focus status is being determined.
+ * @returns {Boolean} Returns `true` if the tab is both animation-focused and screen-focused, otherwise `false`.
+ *
+ * @example
+ * const isTabFocused = useTabNavigatorFocus({ tabIndex: 1 });
+ */
+function useTabNavigatorFocus({tabIndex}) {
+ let tabPositionAnimation = null;
+ try {
+ // Retrieve the animation value from the tab navigator, which ranges from 0 to the total number of pages displayed.
+ // Even a minimal scroll towards the camera page (e.g., a value of 0.001 at start) should activate the camera for immediate responsiveness.
+ // STOP!!!!!!! This is not a pattern to be followed! We are conditionally rendering this hook becase when used in the edit flow we'll never be inside a tab navigator.
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ tabPositionAnimation = useTabAnimation();
+ } catch (error) {
+ tabPositionAnimation = null;
+ }
+ const isPageFocused = useIsFocused();
+ // set to true if the hook is not used within the MaterialTopTabs context
+ // the hook will then return true if the screen is focused
+ const [isTabFocused, setIsTabFocused] = useState(!tabPositionAnimation);
+
+ useEffect(() => {
+ if (!tabPositionAnimation) {
+ return;
+ }
+ const index = Number(tabIndex);
+
+ const listenerId = tabPositionAnimation.addListener(({value}) => {
+ // Activate camera as soon the index is animating towards the `tabIndex`
+ DomUtils.requestAnimationFrame(() => {
+ setIsTabFocused(value > index - 1 && value < index + 1);
+ });
+ });
+
+ // We need to get the position animation value on component initialization to determine
+ // if the tab is focused or not. Since it's an Animated.Value the only synchronous way
+ // to retrieve the value is to use a private method.
+ // eslint-disable-next-line no-underscore-dangle
+ const initialTabPositionValue = tabPositionAnimation.__getValue();
+
+ if (typeof initialTabPositionValue === 'number') {
+ DomUtils.requestAnimationFrame(() => {
+ setIsTabFocused(initialTabPositionValue > index - 1 && initialTabPositionValue < index + 1);
+ });
+ }
+
+ return () => {
+ if (!tabPositionAnimation) {
+ return;
+ }
+ tabPositionAnimation.removeListener(listenerId);
+ };
+ }, [tabIndex, tabPositionAnimation]);
+
+ return isTabFocused && isPageFocused;
+}
+
+export default useTabNavigatorFocus;
diff --git a/src/libs/DomUtils/index.native.ts b/src/libs/DomUtils/index.native.ts
index 9a9758228776..0864f1a16ac0 100644
--- a/src/libs/DomUtils/index.native.ts
+++ b/src/libs/DomUtils/index.native.ts
@@ -2,6 +2,15 @@ import GetActiveElement from './types';
const getActiveElement: GetActiveElement = () => null;
+const requestAnimationFrame = (callback: () => void) => {
+ if (!callback) {
+ return;
+ }
+
+ callback();
+};
+
export default {
getActiveElement,
+ requestAnimationFrame,
};
diff --git a/src/libs/DomUtils/index.ts b/src/libs/DomUtils/index.ts
index 94dd54547454..6a2eed57fbe6 100644
--- a/src/libs/DomUtils/index.ts
+++ b/src/libs/DomUtils/index.ts
@@ -4,4 +4,5 @@ const getActiveElement: GetActiveElement = () => document.activeElement;
export default {
getActiveElement,
+ requestAnimationFrame: window.requestAnimationFrame.bind(window),
};
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index 33ddd77ed8c8..aedb2fa8d741 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -15,7 +15,7 @@ import * as SessionUtils from '@libs/SessionUtils';
import DemoSetupPage from '@pages/DemoSetupPage';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import DesktopSignInRedirectPage from '@pages/signin/DesktopSignInRedirectPage';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as App from '@userActions/App';
import * as Download from '@userActions/Download';
import * as Modal from '@userActions/Modal';
@@ -142,6 +142,7 @@ const defaultProps = {
};
function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, session, lastOpenedPublicRoomID, demoInfo}) {
+ const styles = useThemeStyles();
const {isSmallScreenWidth} = useWindowDimensions();
const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth);
const isInitialRender = useRef(true);
diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay.js b/src/libs/Navigation/AppNavigator/Navigators/Overlay.js
index c030b91cf930..7a4cbf7db3c5 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/Overlay.js
+++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay.js
@@ -4,7 +4,7 @@ import React from 'react';
import {Animated, View} from 'react-native';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import useLocalize from '@hooks/useLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -13,6 +13,7 @@ const propTypes = {
};
function Overlay(props) {
+ const styles = useThemeStyles();
const {current} = useCardAnimation();
const {translate} = useLocalize();
@@ -20,9 +21,9 @@ function Overlay(props) {
{/* In the latest Electron version buttons can't be both clickable and draggable.
- That's why we added this workaround. Because of two Pressable components on the desktop app
- we have 30px draggable ba at the top and the rest of the dimmed area is clickable. On other devices,
- everything behaves normally like one big pressable */}
+ That's why we added this workaround. Because of two Pressable components on the desktop app
+ we have 30px draggable ba at the top and the rest of the dimmed area is clickable. On other devices,
+ everything behaves normally like one big pressable */}
) {
return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED;
}
+function isChannelLogMemberAction(reportAction: OnyxEntry) {
+ return (
+ reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM ||
+ reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM ||
+ reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM ||
+ reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.REMOVE_FROM_ROOM
+ );
+}
+
/**
* Returns whether the comment is a thread parent message/the first message in a thread
*/
@@ -461,6 +470,9 @@ function getLastClosedReportAction(reportActions: ReportActions | null): OnyxEnt
* action is always the created action
*/
function getFirstVisibleReportActionID(sortedReportActions: ReportAction[], isOffline: boolean): string {
+ if (!Array.isArray(sortedReportActions)) {
+ return '';
+ }
const sortedFilterReportActions = sortedReportActions.filter((action) => !isDeletedAction(action) || (action?.childVisibleActionCount ?? 0) > 0 || isOffline);
return sortedFilterReportActions.length > 1 ? sortedFilterReportActions[sortedFilterReportActions.length - 2].reportActionID : '';
}
@@ -657,4 +669,5 @@ export {
shouldReportActionBeVisible,
shouldReportActionBeVisibleAsLastAction,
getFirstVisibleReportActionID,
+ isChannelLogMemberAction,
};
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 973ed1f0dfd1..673cb09232de 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -11,10 +11,10 @@ import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvata
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import * as IOU from './actions/IOU';
import * as CurrencyUtils from './CurrencyUtils';
import DateUtils from './DateUtils';
import isReportMessageAttachment from './isReportMessageAttachment';
+import * as LocalePhoneNumber from './LocalePhoneNumber';
import * as Localize from './Localize';
import linkingConfig from './Navigation/linkingConfig';
import Navigation from './Navigation/Navigation';
@@ -81,6 +81,25 @@ Onyx.connect({
callback: (val) => (loginList = val),
});
+let allPolicyTags = {};
+
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.POLICY_TAGS,
+ waitForCollectionCallback: true,
+ callback: (value) => {
+ if (!value) {
+ allPolicyTags = {};
+ return;
+ }
+
+ allPolicyTags = value;
+ },
+});
+
+function getPolicyTags(policyID) {
+ return lodashGet(allPolicyTags, `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {});
+}
+
function getChatType(report) {
return report ? report.chatType : '';
}
@@ -1259,16 +1278,20 @@ function getDisplayNameForParticipant(accountID, shouldUseShortForm = false, sho
if (!accountID) {
return '';
}
+
const personalDetails = getPersonalDetailsForAccountID(accountID);
- // this is to check if account is an invite/optimistically created one
+ const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetails.login || '');
+
+ // This is to check if account is an invite/optimistically created one
// and prevent from falling back to 'Hidden', so a correct value is shown
- // when searching for a new user
+ // when searching for a new user while offline
if (lodashGet(personalDetails, 'isOptimisticPersonalDetail') === true) {
- return personalDetails.login || '';
+ return formattedLogin;
}
- const longName = personalDetails.displayName;
+
+ const longName = personalDetails.displayName || formattedLogin;
const shortName = personalDetails.firstName || longName;
- if (!longName && !personalDetails.login && shouldFallbackToHidden) {
+ if (!longName && shouldFallbackToHidden) {
return Localize.translateLocal('common.hidden');
}
return shouldUseShortForm ? shortName : longName;
@@ -1629,9 +1652,10 @@ function getTransactionDetails(transaction, createdDateFormat = CONST.DATE.FNS_F
* - or the user is an admin on the policy the expense report is tied to
*
* @param {Object} reportAction
+ * @param {String} fieldToEdit
* @returns {Boolean}
*/
-function canEditMoneyRequest(reportAction) {
+function canEditMoneyRequest(reportAction, fieldToEdit = '') {
const isDeleted = ReportActionsUtils.isDeletedAction(reportAction);
if (isDeleted) {
@@ -1653,7 +1677,9 @@ function canEditMoneyRequest(reportAction) {
const isReportSettled = isSettled(moneyRequestReport.reportID);
const isAdmin = isExpenseReport(moneyRequestReport) && lodashGet(getPolicy(moneyRequestReport.policyID), 'role', '') === CONST.POLICY.ROLE.ADMIN;
const isRequestor = currentUserAccountID === reportAction.actorAccountID;
-
+ if (isAdmin && !isRequestor && fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) {
+ return false;
+ }
if (isAdmin) {
return true;
}
@@ -1680,7 +1706,7 @@ function canEditFieldOfMoneyRequest(reportAction, reportID, fieldToEdit) {
];
// Checks if this user has permissions to edit this money request
- if (!canEditMoneyRequest(reportAction)) {
+ if (!canEditMoneyRequest(reportAction, fieldToEdit)) {
return false; // User doesn't have permission to edit
}
@@ -1921,7 +1947,7 @@ function getModifiedExpenseMessage(reportAction) {
}
const reportID = lodashGet(reportAction, 'reportID', '');
const policyID = lodashGet(getReport(reportID), 'policyID', '');
- const policyTags = IOU.getPolicyTags(policyID);
+ const policyTags = getPolicyTags(policyID);
const policyTag = PolicyUtils.getTag(policyTags);
const policyTagListName = lodashGet(policyTag, 'name', Localize.translateLocal('common.tag'));
@@ -4144,6 +4170,48 @@ function getIOUReportActionDisplayMessage(reportAction) {
});
}
+/**
+ * Return room channel log display message
+ *
+ * @param {Object} reportAction
+ * @returns {String}
+ */
+function getChannelLogMemberMessage(reportAction) {
+ const verb =
+ reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM
+ ? 'invited'
+ : 'removed';
+
+ const mentions = _.map(reportAction.originalMessage.targetAccountIDs, (accountID) => {
+ const personalDetail = lodashGet(allPersonalDetails, accountID);
+ const displayNameOrLogin =
+ LocalePhoneNumber.formatPhoneNumber(lodashGet(personalDetail, 'login', '')) || lodashGet(personalDetail, 'displayName', '') || Localize.translateLocal('common.hidden');
+ return `@${displayNameOrLogin}`;
+ });
+
+ const lastMention = mentions.pop();
+ let message = '';
+
+ if (mentions.length === 0) {
+ message = `${verb} ${lastMention}`;
+ } else if (mentions.length === 1) {
+ message = `${verb} ${mentions[0]} and ${lastMention}`;
+ } else {
+ message = `${verb} ${mentions.join(', ')}, and ${lastMention}`;
+ }
+
+ const roomName = lodashGet(reportAction, 'originalMessage.roomName', '');
+ if (roomName) {
+ const preposition =
+ reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM
+ ? ' to'
+ : ' from';
+ message += `${preposition} ${roomName}`;
+ }
+
+ return message;
+}
+
/**
* Checks if a report is a group chat.
*
@@ -4196,6 +4264,15 @@ function getRoom(type, policyID) {
const room = _.find(allReports, (report) => report && report.policyID === policyID && report.chatType === type && !isThread(report));
return room;
}
+/**
+ * We only want policy owners and admins to be able to modify the welcome message, but not in thread chat.
+ * @param {Object} report
+ * @param {Object} policy
+ * @return {Boolean}
+ */
+function shouldDisableWelcomeMessage(report, policy) {
+ return isMoneyRequestReport(report) || isArchivedRoom(report) || !isChatRoom(report) || isChatThread(report) || !PolicyUtils.isPolicyAdmin(policy);
+}
export {
getReportParticipantsTitle,
@@ -4357,5 +4434,7 @@ export {
parseReportRouteParams,
getReimbursementQueuedActionMessage,
getPersonalDetailsForAccountID,
+ getChannelLogMemberMessage,
getRoom,
+ shouldDisableWelcomeMessage,
};
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index 158c1960895a..1f1cee166a0e 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -254,7 +254,10 @@ function buildOnyxDataForMoneyRequest(
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`,
- value: {pendingAction: null},
+ value: {
+ pendingAction: null,
+ pendingFields: null,
+ },
},
{
onyxMethod: Onyx.METHOD.MERGE,
@@ -301,6 +304,7 @@ function buildOnyxDataForMoneyRequest(
hasOutstandingIOU: chatReport.hasOutstandingIOU,
iouReportID: chatReport.iouReportID,
lastReadTime: chatReport.lastReadTime,
+ pendingFields: null,
...(isNewChatReport
? {
errorFields: {
@@ -316,6 +320,7 @@ function buildOnyxDataForMoneyRequest(
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
value: {
+ pendingFields: null,
errorFields: {
createChat: ErrorUtils.getMicroSecondOnyxError('report.genericCreateReportFailureMessage'),
},
@@ -328,6 +333,8 @@ function buildOnyxDataForMoneyRequest(
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`,
value: {
errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'),
+ pendingAction: null,
+ pendingFields: null,
},
},
{
@@ -2951,10 +2958,6 @@ function getIOUReportID(iou, route) {
return lodashGet(route, 'params.reportID') || lodashGet(iou, 'participants.0.reportID', '');
}
-function getPolicyTags(policyID) {
- return lodashGet(allPolicyTags, `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {});
-}
-
export {
createDistanceRequest,
editMoneyRequest,
@@ -2991,5 +2994,4 @@ export {
replaceReceipt,
detachReceipt,
getIOUReportID,
- getPolicyTags,
};
diff --git a/src/libs/actions/Modal.ts b/src/libs/actions/Modal.ts
index 5114d5c1f42f..39016b241585 100644
--- a/src/libs/actions/Modal.ts
+++ b/src/libs/actions/Modal.ts
@@ -1,13 +1,13 @@
import Onyx from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';
-let closeModal: (isNavigating: boolean) => void;
+let closeModal: ((isNavigating: boolean) => void) | null;
let onModalClose: null | (() => void);
/**
* Allows other parts of the app to call modal close function
*/
-function setCloseModal(onClose: () => void) {
+function setCloseModal(onClose: (() => void) | null) {
closeModal = onClose;
}
diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts
index b44b485ac60f..ce673fa6aaaf 100644
--- a/src/libs/actions/OnyxUpdates.ts
+++ b/src/libs/actions/OnyxUpdates.ts
@@ -15,6 +15,10 @@ Onyx.connect({
callback: (val) => (lastUpdateIDAppliedToClient = val),
});
+// This promise is used to ensure pusher events are always processed in the order they are received,
+// even when such events are received over multiple separate pusher updates.
+let pusherEventsPromise = Promise.resolve();
+
function applyHTTPSOnyxUpdates(request: Request, response: Response) {
console.debug('[OnyxUpdateManager] Applying https update');
// For most requests we can immediately update Onyx. For write requests we queue the updates and apply them after the sequential queue has flushed to prevent a replay effect in
@@ -44,11 +48,17 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) {
}
function applyPusherOnyxUpdates(updates: OnyxUpdateEvent[]) {
- console.debug('[OnyxUpdateManager] Applying pusher update');
- const pusherEventPromises = updates.map((update) => PusherUtils.triggerMultiEventHandler(update.eventType, update.data));
- return Promise.all(pusherEventPromises).then(() => {
- console.debug('[OnyxUpdateManager] Done applying Pusher update');
+ pusherEventsPromise = pusherEventsPromise.then(() => {
+ console.debug('[OnyxUpdateManager] Applying pusher update');
});
+
+ pusherEventsPromise = updates
+ .reduce((promise, update) => promise.then(() => PusherUtils.triggerMultiEventHandler(update.eventType, update.data)), pusherEventsPromise)
+ .then(() => {
+ console.debug('[OnyxUpdateManager] Done applying Pusher update');
+ });
+
+ return pusherEventsPromise;
}
/**
diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts
index db024e8db4cc..2d51fbb9e8d2 100644
--- a/src/libs/actions/PersonalDetails.ts
+++ b/src/libs/actions/PersonalDetails.ts
@@ -99,16 +99,20 @@ function extractFirstAndLastNameFromAvailableDetails({login, displayName, firstN
return {firstName: '', lastName: ''};
}
- const firstSpaceIndex = displayName.indexOf(' ');
- const lastSpaceIndex = displayName.lastIndexOf(' ');
- if (firstSpaceIndex === -1) {
- return {firstName: displayName, lastName: ''};
+ if (displayName) {
+ const firstSpaceIndex = displayName.indexOf(' ');
+ const lastSpaceIndex = displayName.lastIndexOf(' ');
+ if (firstSpaceIndex === -1) {
+ return {firstName: displayName, lastName: ''};
+ }
+
+ return {
+ firstName: displayName.substring(0, firstSpaceIndex).trim(),
+ lastName: displayName.substring(lastSpaceIndex).trim(),
+ };
}
- return {
- firstName: displayName.substring(0, firstSpaceIndex).trim(),
- lastName: displayName.substring(lastSpaceIndex).trim(),
- };
+ return {firstName: '', lastName: ''};
}
/**
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 22a1bc5441e6..58d7a9399533 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -3,7 +3,7 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import Str from 'expensify-common/lib/str';
import lodashDebounce from 'lodash/debounce';
import lodashGet from 'lodash/get';
-import {InteractionManager} from 'react-native';
+import {DeviceEventEmitter, InteractionManager} from 'react-native';
import Onyx from 'react-native-onyx';
import _ from 'underscore';
import * as ActiveClientManager from '@libs/ActiveClientManager';
@@ -937,6 +937,7 @@ function markCommentAsUnread(reportID, reportActionCreated) {
],
},
);
+ DeviceEventEmitter.emit(`unreadAction_${reportID}`, lastReadTime);
}
/**
diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts
index 285fd5b251df..65795e660df0 100644
--- a/src/libs/actions/Transaction.ts
+++ b/src/libs/actions/Transaction.ts
@@ -59,7 +59,7 @@ function addStop(transactionID: string) {
function saveWaypoint(transactionID: string, index: string, waypoint: RecentWaypoint | null, isEditingWaypoint = false) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {
pendingFields: {
- comment: isEditingWaypoint ? CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE : CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ waypoints: isEditingWaypoint ? CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE : CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
comment: {
waypoints: {
diff --git a/src/libs/migrations/CheckForPreviousReportActionID.js b/src/libs/migrations/CheckForPreviousReportActionID.ts
similarity index 64%
rename from src/libs/migrations/CheckForPreviousReportActionID.js
rename to src/libs/migrations/CheckForPreviousReportActionID.ts
index 35f862fd5b3a..1f31a3586b0a 100644
--- a/src/libs/migrations/CheckForPreviousReportActionID.js
+++ b/src/libs/migrations/CheckForPreviousReportActionID.ts
@@ -1,12 +1,9 @@
-import Onyx from 'react-native-onyx';
-import _ from 'underscore';
+import Onyx, {OnyxCollection} from 'react-native-onyx';
import Log from '@libs/Log';
import ONYXKEYS from '@src/ONYXKEYS';
+import * as OnyxTypes from '@src/types/onyx';
-/**
- * @returns {Promise
-
+
>
);
diff --git a/src/pages/EnablePayments/FailedKYC.js b/src/pages/EnablePayments/FailedKYC.js
index 398537cf7645..de170bec4070 100644
--- a/src/pages/EnablePayments/FailedKYC.js
+++ b/src/pages/EnablePayments/FailedKYC.js
@@ -3,7 +3,7 @@ import {View} from 'react-native';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -11,6 +11,7 @@ const propTypes = {
};
function FailedKYC(props) {
+ const styles = useThemeStyles();
return (
diff --git a/src/pages/EnablePayments/IdologyQuestions.js b/src/pages/EnablePayments/IdologyQuestions.js
index a46e6d54a5ed..97c0f55f27c6 100644
--- a/src/pages/EnablePayments/IdologyQuestions.js
+++ b/src/pages/EnablePayments/IdologyQuestions.js
@@ -12,7 +12,7 @@ import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import * as ErrorUtils from '@libs/ErrorUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -52,6 +52,7 @@ const defaultProps = {
};
function IdologyQuestions({questions, walletAdditionalDetails, idNumber}) {
+ const styles = useThemeStyles();
const formRef = useRef();
const {translate} = useLocalize();
diff --git a/src/pages/EnablePayments/OnfidoPrivacy.js b/src/pages/EnablePayments/OnfidoPrivacy.js
index db5098777744..c542df0196cf 100644
--- a/src/pages/EnablePayments/OnfidoPrivacy.js
+++ b/src/pages/EnablePayments/OnfidoPrivacy.js
@@ -12,7 +12,7 @@ import TextLink from '@components/TextLink';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import ONYXKEYS from '@src/ONYXKEYS';
import walletOnfidoDataPropTypes from './walletOnfidoDataPropTypes';
@@ -36,6 +36,7 @@ const defaultProps = {
};
function OnfidoPrivacy({walletOnfidoData, translate, form}) {
+ const styles = useThemeStyles();
const {isLoading = false, hasAcceptedPrivacyPolicy} = walletOnfidoData;
const formRef = useRef(null);
diff --git a/src/pages/EnablePayments/TermsPage/ShortTermsForm.js b/src/pages/EnablePayments/TermsPage/ShortTermsForm.js
index 46a1867a2606..898aec9b2c16 100644
--- a/src/pages/EnablePayments/TermsPage/ShortTermsForm.js
+++ b/src/pages/EnablePayments/TermsPage/ShortTermsForm.js
@@ -4,7 +4,7 @@ import Text from '@components/Text';
import TextLink from '@components/TextLink';
import * as Localize from '@libs/Localize';
import userWalletPropTypes from '@pages/EnablePayments/userWalletPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -17,6 +17,7 @@ const defaultProps = {
};
function ShortTermsForm(props) {
+ const styles = useThemeStyles();
return (
<>
diff --git a/src/pages/EnablePayments/TermsStep.js b/src/pages/EnablePayments/TermsStep.js
index 6dff94b7202b..368f92f0db27 100644
--- a/src/pages/EnablePayments/TermsStep.js
+++ b/src/pages/EnablePayments/TermsStep.js
@@ -9,7 +9,7 @@ import TextLink from '@components/TextLink';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import ONYXKEYS from '@src/ONYXKEYS';
import LongTermsForm from './TermsPage/LongTermsForm';
@@ -33,6 +33,7 @@ const defaultProps = {
};
function TermsStep(props) {
+ const styles = useThemeStyles();
const [hasAcceptedDisclosure, setHasAcceptedDisclosure] = useState(false);
const [hasAcceptedPrivacyPolicyAndWalletAgreement, setHasAcceptedPrivacyPolicyAndWalletAgreement] = useState(false);
const [error, setError] = useState(false);
diff --git a/src/pages/ErrorPage/ErrorBodyText/index.js b/src/pages/ErrorPage/ErrorBodyText/index.js
index d50d15c15cd0..d3436a2fc0a8 100644
--- a/src/pages/ErrorPage/ErrorBodyText/index.js
+++ b/src/pages/ErrorPage/ErrorBodyText/index.js
@@ -2,7 +2,7 @@ import React from 'react';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -10,6 +10,7 @@ const propTypes = {
};
function ErrorBodyText(props) {
+ const styles = useThemeStyles();
return (
{`${props.translate('genericErrorPage.body.helpTextMobile')} `}
diff --git a/src/pages/ErrorPage/GenericErrorPage.js b/src/pages/ErrorPage/GenericErrorPage.js
index af551fe5743a..7b627a8e18d5 100644
--- a/src/pages/ErrorPage/GenericErrorPage.js
+++ b/src/pages/ErrorPage/GenericErrorPage.js
@@ -9,9 +9,9 @@ import SafeAreaConsumer from '@components/SafeAreaConsumer';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import defaultTheme from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
@@ -22,6 +22,8 @@ const propTypes = {
};
function GenericErrorPage({translate}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {resetBoundary} = useErrorBoundary();
return (
@@ -35,7 +37,7 @@ function GenericErrorPage({translate}) {
src={Expensicons.Bug}
height={variables.componentSizeNormal}
width={variables.componentSizeNormal}
- fill={defaultTheme.iconSuccessFill}
+ fill={theme.iconSuccessFill}
/>
@@ -79,7 +81,7 @@ function GenericErrorPage({translate}) {
diff --git a/src/pages/FlagCommentPage.js b/src/pages/FlagCommentPage.js
index ee7b2d8af3c1..721bc6742c4b 100644
--- a/src/pages/FlagCommentPage.js
+++ b/src/pages/FlagCommentPage.js
@@ -14,7 +14,7 @@ import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Report from '@userActions/Report';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
@@ -62,6 +62,7 @@ function getReportID(route) {
}
function FlagCommentPage(props) {
+ const styles = useThemeStyles();
const severities = [
{
severity: CONST.MODERATION.FLAG_SEVERITY_SPAM,
diff --git a/src/pages/GetAssistancePage.js b/src/pages/GetAssistancePage.js
index 647734a0cde1..b801a91af728 100644
--- a/src/pages/GetAssistancePage.js
+++ b/src/pages/GetAssistancePage.js
@@ -12,7 +12,7 @@ import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Link from '@userActions/Link';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
@@ -44,6 +44,7 @@ const defaultProps = {
};
function GetAssistancePage(props) {
+ const styles = useThemeStyles();
const menuItems = [
{
title: props.translate('getAssistancePage.chatWithConcierge'),
diff --git a/src/pages/KeyboardShortcutsPage.js b/src/pages/KeyboardShortcutsPage.js
index 4caade5b9ec1..fde4bf5ff3df 100644
--- a/src/pages/KeyboardShortcutsPage.js
+++ b/src/pages/KeyboardShortcutsPage.js
@@ -7,10 +7,11 @@ import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import KeyboardShortcut from '@libs/KeyboardShortcut';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
function KeyboardShortcutsPage() {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const shortcuts = _.chain(CONST.KEYBOARD_SHORTCUTS)
.filter((shortcut) => !_.isEmpty(shortcut.descriptionKey))
diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.js b/src/pages/LogInWithShortLivedAuthTokenPage.js
index 84de67e496b5..8534961b3337 100644
--- a/src/pages/LogInWithShortLivedAuthTokenPage.js
+++ b/src/pages/LogInWithShortLivedAuthTokenPage.js
@@ -11,8 +11,8 @@ import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Session from '@userActions/Session';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -46,6 +46,8 @@ const defaultProps = {
};
function LogInWithShortLivedAuthTokenPage(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {translate} = useLocalize();
useEffect(() => {
@@ -109,7 +111,7 @@ function LogInWithShortLivedAuthTokenPage(props) {
diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js
index 22af82041f7b..90b2615f901c 100755
--- a/src/pages/NewChatPage.js
+++ b/src/pages/NewChatPage.js
@@ -17,7 +17,7 @@ import compose from '@libs/compose';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import Permissions from '@libs/Permissions';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
@@ -53,6 +53,7 @@ const defaultProps = {
const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE);
function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, isSearchingForReports}) {
+ const styles = useThemeStyles();
const [searchTerm, setSearchTerm] = useState('');
const [filteredRecentReports, setFilteredRecentReports] = useState([]);
const [filteredPersonalDetails, setFilteredPersonalDetails] = useState([]);
diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.js b/src/pages/PrivateNotes/PrivateNotesEditPage.js
index f38dabee9183..c4946d3c07b4 100644
--- a/src/pages/PrivateNotes/PrivateNotesEditPage.js
+++ b/src/pages/PrivateNotes/PrivateNotesEditPage.js
@@ -22,7 +22,7 @@ import updateMultilineInputRange from '@libs/UpdateMultilineInputRange';
import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound';
import personalDetailsPropType from '@pages/personalDetailsPropType';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -50,6 +50,7 @@ const defaultProps = {
};
function PrivateNotesEditPage({route, personalDetailsList, report}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
// We need to edit the note in markdown format, but display it in HTML format
diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.js b/src/pages/PrivateNotes/PrivateNotesListPage.js
index 4d5b348c4b9f..7bcf9c22690b 100644
--- a/src/pages/PrivateNotes/PrivateNotesListPage.js
+++ b/src/pages/PrivateNotes/PrivateNotesListPage.js
@@ -17,7 +17,7 @@ import * as UserUtils from '@libs/UserUtils';
import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound';
import personalDetailsPropType from '@pages/personalDetailsPropType';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -55,6 +55,7 @@ const defaultProps = {
};
function PrivateNotesListPage({report, personalDetailsList, session}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
/**
diff --git a/src/pages/PrivateNotes/PrivateNotesViewPage.js b/src/pages/PrivateNotes/PrivateNotesViewPage.js
index 2b836036448d..76057d3d74bc 100644
--- a/src/pages/PrivateNotes/PrivateNotesViewPage.js
+++ b/src/pages/PrivateNotes/PrivateNotesViewPage.js
@@ -15,7 +15,7 @@ import Navigation from '@libs/Navigation/Navigation';
import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound';
import personalDetailsPropType from '@pages/personalDetailsPropType';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -51,6 +51,7 @@ const defaultProps = {
};
function PrivateNotesViewPage({route, personalDetailsList, session, report}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const isCurrentUserNote = Number(session.accountID) === Number(route.params.accountID);
const privateNote = lodashGet(report, ['privateNotes', route.params.accountID, 'note'], '');
diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js
index 6b851bf9e5b2..4b3c927ef317 100755
--- a/src/pages/ProfilePage.js
+++ b/src/pages/ProfilePage.js
@@ -29,7 +29,7 @@ import Permissions from '@libs/Permissions';
import * as ReportUtils from '@libs/ReportUtils';
import * as UserUtils from '@libs/UserUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import * as PersonalDetails from '@userActions/PersonalDetails';
import * as Report from '@userActions/Report';
@@ -100,6 +100,7 @@ const getPhoneNumber = (details) => {
};
function ProfilePage(props) {
+ const styles = useThemeStyles();
const accountID = Number(lodashGet(props.route.params, 'accountID', 0));
const details = lodashGet(props.personalDetails, accountID, ValidationUtils.isValidAccountRoute(accountID) ? {} : {isloading: false});
diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js
index 8f2d2e07998e..b52a8c4c9a62 100644
--- a/src/pages/ReimbursementAccount/ACHContractStep.js
+++ b/src/pages/ReimbursementAccount/ACHContractStep.js
@@ -5,14 +5,15 @@ import React, {useState} from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import CheckboxWithLabel from '@components/CheckboxWithLabel';
-import Form from '@components/Form';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import withLocalize from '@components/withLocalize';
import * as ValidationUtils from '@libs/ValidationUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import * as FormActions from '@userActions/FormActions';
import CONST from '@src/CONST';
@@ -28,6 +29,7 @@ const propTypes = {
};
function ACHContractStep(props) {
+ const styles = useThemeStyles();
const [beneficialOwners, setBeneficialOwners] = useState(() =>
lodashGet(props.reimbursementAccountDraft, 'beneficialOwners', lodashGet(props.reimbursementAccount, 'achData.beneficialOwners', [])),
);
@@ -156,7 +158,7 @@ function ACHContractStep(props) {
shouldShowGetAssistanceButton
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT}
/>
-
+
);
}
diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js
index 260418b9f50b..ce4df7ae665f 100644
--- a/src/pages/ReimbursementAccount/AddressForm.js
+++ b/src/pages/ReimbursementAccount/AddressForm.js
@@ -2,9 +2,10 @@ import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import AddressSearch from '@components/AddressSearch';
+import InputWrapper from '@components/Form/InputWrapper';
import StatePicker from '@components/StatePicker';
import TextInput from '@components/TextInput';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -92,10 +93,12 @@ const defaultProps = {
};
function AddressForm(props) {
+ const styles = useThemeStyles();
return (
<>
-
-
-
-
+
{props.translate('bankAccount.validateAccountError')}
)}
@@ -175,7 +178,7 @@ function BankAccountStep(props) {
diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js
index 41f73d1ebf8e..e31988fbb118 100644
--- a/src/pages/ReimbursementAccount/CompanyStep.js
+++ b/src/pages/ReimbursementAccount/CompanyStep.js
@@ -7,9 +7,10 @@ import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import CheckboxWithLabel from '@components/CheckboxWithLabel';
-import DatePicker from '@components/DatePicker';
-import Form from '@components/Form';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import NewDatePicker from '@components/NewDatePicker';
import Picker from '@components/Picker';
import ScreenWrapper from '@components/ScreenWrapper';
import StatePicker from '@components/StatePicker';
@@ -19,7 +20,7 @@ import TextLink from '@components/TextLink';
import withLocalize from '@components/withLocalize';
import compose from '@libs/compose';
import * as ValidationUtils from '@libs/ValidationUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -54,6 +55,7 @@ const defaultProps = {
};
function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaultStateForField, onBackButtonPress, translate, session, user, policyID}) {
+ const styles = useThemeStyles();
/**
* @param {Array} fieldNames
*
@@ -151,7 +153,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT}
onBackButtonPress={onBackButtonPress}
/>
-
+
);
}
diff --git a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js
index 52ccc3b87835..45c503494c3d 100644
--- a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js
+++ b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js
@@ -14,7 +14,7 @@ import Section from '@components/Section';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import WorkspaceResetBankAccountModal from '@pages/workspace/WorkspaceResetBankAccountModal';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import CONST from '@src/CONST';
import * as ReimbursementAccountProps from './reimbursementAccountPropTypes';
@@ -38,6 +38,7 @@ const propTypes = {
const defaultProps = {policyName: ''};
function ContinueBankAccountSetup(props) {
+ const styles = useThemeStyles();
const errors = lodashGet(props.reimbursementAccount, 'errors', {});
const pendingAction = lodashGet(props.reimbursementAccount, 'pendingAction', null);
return (
diff --git a/src/pages/ReimbursementAccount/Enable2FAPrompt.js b/src/pages/ReimbursementAccount/Enable2FAPrompt.js
index c768f901508e..3cbb3111e089 100644
--- a/src/pages/ReimbursementAccount/Enable2FAPrompt.js
+++ b/src/pages/ReimbursementAccount/Enable2FAPrompt.js
@@ -5,7 +5,7 @@ import * as Illustrations from '@components/Icon/Illustrations';
import Section from '@components/Section';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Link from '@userActions/Link';
import ROUTES from '@src/ROUTES';
@@ -13,6 +13,7 @@ const propTypes = {
...withLocalizePropTypes,
};
function Enable2FAPrompt(props) {
+ const styles = useThemeStyles();
const secureYourAccountUrl = encodeURI(
`settings?param={"section":"account","action":"enableTwoFactorAuth","exitTo":"${ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute()}","isFromNewDot":"true"}`,
);
diff --git a/src/pages/ReimbursementAccount/EnableStep.js b/src/pages/ReimbursementAccount/EnableStep.js
index cb59a4bc97f5..4f4285d1a62b 100644
--- a/src/pages/ReimbursementAccount/EnableStep.js
+++ b/src/pages/ReimbursementAccount/EnableStep.js
@@ -18,7 +18,7 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import compose from '@libs/compose';
import userPropTypes from '@pages/settings/userPropTypes';
import WorkspaceResetBankAccountModal from '@pages/workspace/WorkspaceResetBankAccountModal';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Link from '@userActions/Link';
import * as BankAccounts from '@userActions/ReimbursementAccount';
import CONST from '@src/CONST';
@@ -47,6 +47,7 @@ const defaultProps = {
};
function EnableStep(props) {
+ const styles = useThemeStyles();
const isUsingExpensifyCard = props.user.isUsingExpensifyCard;
const achData = lodashGet(props.reimbursementAccount, 'achData') || {};
const {icon, iconSize} = getBankIcon(achData.bankName);
diff --git a/src/pages/ReimbursementAccount/IdentityForm.js b/src/pages/ReimbursementAccount/IdentityForm.js
index f584ca3f665a..f762dbd28954 100644
--- a/src/pages/ReimbursementAccount/IdentityForm.js
+++ b/src/pages/ReimbursementAccount/IdentityForm.js
@@ -3,9 +3,10 @@ import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
-import DatePicker from '@components/DatePicker';
+import InputWrapper from '@components/Form/InputWrapper';
+import NewDatePicker from '@components/NewDatePicker';
import TextInput from '@components/TextInput';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import AddressForm from './AddressForm';
@@ -130,6 +131,7 @@ const defaultProps = {
};
function IdentityForm(props) {
+ const styles = useThemeStyles();
// dob field has multiple validations/errors, we are handling it temporarily like this.
const dobErrorText = (props.errors.dob ? props.translate('bankAccount.error.dob') : '') || (props.errors.dobAge ? props.translate('bankAccount.error.age') : '');
const identityFormInputKeys = ['firstName', 'lastName', 'dob', 'ssnLast4'];
@@ -141,7 +143,8 @@ function IdentityForm(props) {
-
-
-
- Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
/>
-
+
+
{errorText}
@@ -568,4 +570,5 @@ export default compose(
}),
withLocalize,
withPolicy,
+ withThemeStyles,
)(ReimbursementAccountPage);
diff --git a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js
index 851869f9128d..dc6cff300b09 100644
--- a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js
+++ b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js
@@ -9,7 +9,7 @@ import Onfido from '@components/Onfido';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import Growl from '@libs/Growl';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -34,6 +34,7 @@ const HEADER_STEP_COUNTER = {step: 3, total: 5};
const ONFIDO_ERROR_DISPLAY_DURATION = 10000;
function RequestorOnfidoStep({onBackButtonPress, reimbursementAccount, onfidoToken}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const submitOnfidoData = (onfidoData) => {
diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js
index 4a3dd2f0917f..d894753d6025 100644
--- a/src/pages/ReimbursementAccount/RequestorStep.js
+++ b/src/pages/ReimbursementAccount/RequestorStep.js
@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import CheckboxWithLabel from '@components/CheckboxWithLabel';
-import Form from '@components/Form';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
@@ -132,7 +133,7 @@ const RequestorStep = React.forwardRef(({reimbursementAccount, shouldShowOnfido,
onBackButtonPress={onBackButtonPress}
shouldShowGetAssistanceButton
/>
-
-
-
+
);
});
diff --git a/src/pages/ReimbursementAccount/ValidationStep.js b/src/pages/ReimbursementAccount/ValidationStep.js
index 343f98644766..386b6b346467 100644
--- a/src/pages/ReimbursementAccount/ValidationStep.js
+++ b/src/pages/ReimbursementAccount/ValidationStep.js
@@ -21,7 +21,7 @@ import compose from '@libs/compose';
import BankAccount from '@libs/models/BankAccount';
import * as ValidationUtils from '@libs/ValidationUtils';
import WorkspaceResetBankAccountModal from '@pages/workspace/WorkspaceResetBankAccountModal';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
@@ -74,6 +74,7 @@ const filterInput = (amount) => {
};
function ValidationStep({reimbursementAccount, translate, onBackButtonPress, account}) {
+ const styles = useThemeStyles();
/**
* @param {Object} values - form input values passed by the Form component
* @returns {Object}
diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js
index de25fdc3a081..f3f55dee3253 100644
--- a/src/pages/ReportDetailsPage.js
+++ b/src/pages/ReportDetailsPage.js
@@ -22,7 +22,7 @@ import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -60,6 +60,7 @@ const defaultProps = {
};
function ReportDetailsPage(props) {
+ const styles = useThemeStyles();
const policy = useMemo(() => props.policies[`${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}`], [props.policies, props.report.policyID]);
const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy), [policy]);
const isPolicyMember = useMemo(() => PolicyUtils.isPolicyMember(props.report.policyID, props.policies), [props.report.policyID, props.policies]);
diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js
index 1ae6942c6412..376ef35da7cd 100755
--- a/src/pages/ReportParticipantsPage.js
+++ b/src/pages/ReportParticipantsPage.js
@@ -14,7 +14,7 @@ import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import * as UserUtils from '@libs/UserUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -83,6 +83,7 @@ const getAllParticipants = (report, personalDetails, translate) =>
.value();
function ReportParticipantsPage(props) {
+ const styles = useThemeStyles();
const participants = _.map(getAllParticipants(props.report, props.personalDetails, props.translate), (participant) => ({
...participant,
isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID),
diff --git a/src/pages/ReportWelcomeMessagePage.js b/src/pages/ReportWelcomeMessagePage.js
index c748e36c98e6..a0e471d00df6 100644
--- a/src/pages/ReportWelcomeMessagePage.js
+++ b/src/pages/ReportWelcomeMessagePage.js
@@ -13,9 +13,9 @@ import TextInput from '@components/TextInput';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
-import * as PolicyUtils from '@libs/PolicyUtils';
+import * as ReportUtils from '@libs/ReportUtils';
import updateMultilineInputRange from '@libs/UpdateMultilineInputRange';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -45,6 +45,7 @@ const defaultProps = {
};
function ReportWelcomeMessagePage(props) {
+ const styles = useThemeStyles();
const parser = new ExpensiMark();
const [welcomeMessage, setWelcomeMessage] = useState(() => parser.htmlToMarkdown(props.report.welcomeMessage));
const welcomeMessageInputRef = useRef(null);
@@ -80,7 +81,7 @@ function ReportWelcomeMessagePage(props) {
includeSafeAreaPaddingBottom={false}
testID={ReportWelcomeMessagePage.displayName}
>
-
+
Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(props.report.reportID))}
diff --git a/src/pages/RoomInvitePage.js b/src/pages/RoomInvitePage.js
index aedd406d28c0..a1f7d22c3dc3 100644
--- a/src/pages/RoomInvitePage.js
+++ b/src/pages/RoomInvitePage.js
@@ -18,7 +18,7 @@ import Permissions from '@libs/Permissions';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -63,6 +63,7 @@ const defaultProps = {
};
function RoomInvitePage(props) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const [searchTerm, setSearchTerm] = useState('');
const [selectedOptions, setSelectedOptions] = useState([]);
diff --git a/src/pages/RoomMembersPage.js b/src/pages/RoomMembersPage.js
index a2599b3382d7..2b96536c9719 100644
--- a/src/pages/RoomMembersPage.js
+++ b/src/pages/RoomMembersPage.js
@@ -21,7 +21,7 @@ import Permissions from '@libs/Permissions';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as UserUtils from '@libs/UserUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -78,6 +78,7 @@ const defaultProps = {
};
function RoomMembersPage(props) {
+ const styles = useThemeStyles();
const [selectedMembers, setSelectedMembers] = useState([]);
const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false);
const [searchValue, setSearchValue] = useState('');
diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js
index 3e7731efc7b2..09201046d976 100755
--- a/src/pages/SearchPage.js
+++ b/src/pages/SearchPage.js
@@ -9,13 +9,13 @@ import {withNetwork} from '@components/OnyxProvider';
import OptionsSelector from '@components/OptionsSelector';
import ScreenWrapper from '@components/ScreenWrapper';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import Performance from '@libs/Performance';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
import * as Report from '@userActions/Report';
import Timing from '@userActions/Timing';
import CONST from '@src/CONST';
@@ -45,6 +45,7 @@ const propTypes = {
/** Whether we are searching for reports in the server */
isSearchingForReports: PropTypes.bool,
+ ...withThemeStylesPropTypes,
};
const defaultProps = {
@@ -189,7 +190,7 @@ class SearchPage extends Component {
{({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => (
<>
-
+
Navigation.goBack(isReport ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(this.props.report.reportID) : ROUTES.SETTINGS)}
/>
-
-
+
+
Navigation.goBack(ROUTES.HOME)}
illustration={LottieAnimations.SaveTheWorld}
>
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index 0b7ed4aedcd6..98c20a2f75aa 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -28,8 +28,8 @@ import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Link from '@userActions/Link';
import * as Report from '@userActions/Report';
import * as Session from '@userActions/Session';
@@ -73,6 +73,8 @@ const defaultProps = {
};
function HeaderView(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const participants = lodashGet(props.report, 'participantAccountIDs', []);
const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails);
const isMultipleParticipant = participants.length > 1;
@@ -247,7 +249,7 @@ function HeaderView(props) {
)}
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 7abf395644f8..0f09b51487ae 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -26,7 +26,7 @@ import * as ReportUtils from '@libs/ReportUtils';
import personalDetailsPropType from '@pages/personalDetailsPropType';
import reportMetadataPropTypes from '@pages/reportMetadataPropTypes';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as ComposerActions from '@userActions/Composer';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
@@ -147,6 +147,7 @@ function ReportScreen({
userLeavingStatus,
currentReportID,
}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
@@ -427,8 +428,8 @@ function ReportScreen({
)}
{/* Note: The ReportActionsSkeletonView should be allowed to mount even if the initial report actions are not loaded.
- If we prevent rendering the report while they are loading then
- we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
+ If we prevent rendering the report while they are loading then
+ we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
{(!isReportReadyForDisplay || isLoadingInitialReportActions || isLoading) && }
{isReportReadyForDisplay ? (
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js
index 5a1266d15a42..4f35926c5957 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.js
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js
@@ -281,6 +281,9 @@ export default [
} else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) {
const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction);
Clipboard.setString(displayMessage);
+ } else if (ReportActionsUtils.isChannelLogMemberAction(reportAction)) {
+ const logMessage = ReportUtils.getChannelLogMemberMessage(reportAction);
+ Clipboard.setString(logMessage);
} else if (content) {
const parser = new ExpensiMark();
if (!Clipboard.canSetHtml()) {
diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.js b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.js
index 93437393a5c5..873e7fa243af 100644
--- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.js
+++ b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.js
@@ -1,9 +1,10 @@
import React from 'react';
import {Animated, View} from 'react-native';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import floatingMessageCounterContainerPropTypes from './floatingMessageCounterContainerPropTypes';
function FloatingMessageCounterContainer(props) {
+ const styles = useThemeStyles();
return (
{props.children}
diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.js b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.js
index 2137fa07b573..0b721067631c 100644
--- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.js
+++ b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.js
@@ -1,9 +1,10 @@
import React from 'react';
import {Animated} from 'react-native';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import floatingMessageCounterContainerPropTypes from './floatingMessageCounterContainerPropTypes';
function FloatingMessageCounterContainer(props) {
+ const styles = useThemeStyles();
return (
new Animated.Value(MARKER_INACTIVE_TRANSLATE_Y), []);
@@ -72,8 +74,9 @@ function FloatingMessageCounter(props) {
+
= 0 ? Math.min(props.maxAmountOfPreviews, props.linkMetadata.length) : props.linkMetadata.length),
(linkData) => {
@@ -99,7 +101,7 @@ function LinkPreviewer(props) {
{!_.isEmpty(title) && (
{title}
diff --git a/src/pages/home/report/ListBoundaryLoader/ListBoundaryLoader.js b/src/pages/home/report/ListBoundaryLoader/ListBoundaryLoader.js
index 861f6201a53f..6dd56471af07 100644
--- a/src/pages/home/report/ListBoundaryLoader/ListBoundaryLoader.js
+++ b/src/pages/home/report/ListBoundaryLoader/ListBoundaryLoader.js
@@ -3,8 +3,8 @@ import React from 'react';
import {ActivityIndicator, View} from 'react-native';
import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView';
import useNetwork from '@hooks/useNetwork';
-import styles, {stylesGenerator} from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -32,6 +32,8 @@ const defaultProps = {
};
function ListBoundaryLoader({type, isLoadingOlderReportActions, isLoadingInitialReportActions, lastReportActionName, isLoadingNewerReportActions}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {isOffline} = useNetwork();
// we use two different loading components for header and footer to reduce the jumping effect when you scrolling to the newer reports
@@ -56,9 +58,9 @@ function ListBoundaryLoader({type, isLoadingOlderReportActions, isLoadingInitial
// applied for a header of the list, i.e. when you scroll to the bottom of the list
// the styles for android and the rest components are different that's why we use two different components
return (
-
+
diff --git a/src/pages/home/report/ParticipantLocalTime.js b/src/pages/home/report/ParticipantLocalTime.js
index 2ce0054a3e59..5efd53fe6ae7 100644
--- a/src/pages/home/report/ParticipantLocalTime.js
+++ b/src/pages/home/report/ParticipantLocalTime.js
@@ -6,7 +6,7 @@ import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import DateUtils from '@libs/DateUtils';
import Timers from '@libs/Timers';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -29,6 +29,7 @@ function getParticipantLocalTime(participant, preferredLocale) {
}
function ParticipantLocalTime(props) {
+ const styles = useThemeStyles();
const {participant, preferredLocale, translate} = props;
const [localTime, setLocalTime] = useState(() => getParticipantLocalTime(participant, preferredLocale));
diff --git a/src/pages/home/report/ReactionList/BaseReactionList.js b/src/pages/home/report/ReactionList/BaseReactionList.js
index bd1ed436ceaa..f94f6cdfde0d 100755
--- a/src/pages/home/report/ReactionList/BaseReactionList.js
+++ b/src/pages/home/report/ReactionList/BaseReactionList.js
@@ -8,7 +8,7 @@ import participantPropTypes from '@components/participantPropTypes';
import withWindowDimensions from '@components/withWindowDimensions';
import Navigation from '@libs/Navigation/Navigation';
import * as UserUtils from '@libs/UserUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
@@ -58,6 +58,7 @@ const getItemLayout = (_, index) => ({
});
function BaseReactionList(props) {
+ const styles = useThemeStyles();
if (!props.isVisible) {
return null;
}
diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.js b/src/pages/home/report/ReactionList/HeaderReactionList.js
index 49d61f05b191..c07b36d42949 100644
--- a/src/pages/home/report/ReactionList/HeaderReactionList.js
+++ b/src/pages/home/report/ReactionList/HeaderReactionList.js
@@ -6,8 +6,8 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
import compose from '@libs/compose';
import * as EmojiUtils from '@libs/EmojiUtils';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import reactionPropTypes from './reactionPropTypes';
const propTypes = {
@@ -26,6 +26,7 @@ const defaultProps = {
};
function HeaderReactionList(props) {
+ const styles = useThemeStyles();
return (
diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js
index 6ab09a5a1bd4..44183b23e43f 100644
--- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js
+++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
-import React, {useCallback, useEffect, useMemo} from 'react';
+import React, {useMemo} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
@@ -9,15 +9,12 @@ import * as Expensicons from '@components/Icon/Expensicons';
import PopoverMenu from '@components/PopoverMenu';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import Tooltip from '@components/Tooltip/PopoverAnchorTooltip';
-import withNavigationFocus from '@components/withNavigationFocus';
import useLocalize from '@hooks/useLocalize';
-import usePrevious from '@hooks/usePrevious';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
-import compose from '@libs/compose';
import Permissions from '@libs/Permissions';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import * as Report from '@userActions/Report';
import * as Task from '@userActions/Task';
@@ -87,9 +84,6 @@ const propTypes = {
// eslint-disable-next-line react/forbid-prop-types
current: PropTypes.object,
}).isRequired,
-
- /** Whether navigation is focused */
- isFocused: PropTypes.bool.isRequired,
};
const defaultProps = {
@@ -122,8 +116,8 @@ function AttachmentPickerWithMenuItems({
onAddActionPressed,
onItemSelected,
actionButtonRef,
- isFocused,
}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const {windowHeight} = useWindowDimensions();
@@ -171,33 +165,10 @@ function AttachmentPickerWithMenuItems({
];
}, [betas, report, reportID, translate]);
- const onPopoverMenuClose = useCallback(() => {
+ const onPopoverMenuClose = () => {
setMenuVisibility(false);
onMenuClosed();
- }, [onMenuClosed, setMenuVisibility]);
-
- const prevIsFocused = usePrevious(isFocused);
-
- /**
- * Check if current screen is inactive and previous screen is active.
- * Used to close already opened popover menu when any other page is opened over current page.
- *
- * @return {Boolean}
- */
- const didScreenBecomeInactive = useCallback(
- () =>
- // When any other page is opened over LHN
- !isFocused && prevIsFocused,
- [isFocused, prevIsFocused],
- );
-
- // When the navigation is focused, we want to close the popover menu.
- useEffect(() => {
- if (!didScreenBecomeInactive()) {
- return;
- }
- onPopoverMenuClose();
- }, [didScreenBecomeInactive, onPopoverMenuClose]);
+ };
return (
@@ -269,10 +240,6 @@ function AttachmentPickerWithMenuItems({
ref={actionButtonRef}
onPress={(e) => {
e.preventDefault();
- if (!isFocused) {
- return;
- }
-
onAddActionPressed();
// Drop focus to avoid blue focus ring.
@@ -290,7 +257,7 @@ function AttachmentPickerWithMenuItems({
{
setMenuVisibility(false);
@@ -320,11 +287,8 @@ AttachmentPickerWithMenuItems.propTypes = propTypes;
AttachmentPickerWithMenuItems.defaultProps = defaultProps;
AttachmentPickerWithMenuItems.displayName = 'AttachmentPickerWithMenuItems';
-export default compose(
- withNavigationFocus,
- withOnyx({
- betas: {
- key: ONYXKEYS.BETAS,
- },
- }),
-)(AttachmentPickerWithMenuItems);
+export default withOnyx({
+ betas: {
+ key: ONYXKEYS.BETAS,
+ },
+})(AttachmentPickerWithMenuItems);
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
index 3f943502f748..e376e8481c0c 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
@@ -28,8 +28,8 @@ import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutsi
import SilentCommentUpdater from '@pages/home/report/ReportActionCompose/SilentCommentUpdater';
import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions';
import containerComposeStyles from '@styles/containerComposeStyles';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import * as EmojiPickerActions from '@userActions/EmojiPickerAction';
import * as InputFocus from '@userActions/InputFocus';
import * as Report from '@userActions/Report';
@@ -107,6 +107,8 @@ function ComposerWithSuggestions({
// For testing
children,
}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {preferredLocale} = useLocalize();
const isFocused = useIsFocused();
const navigation = useNavigation();
@@ -533,7 +535,7 @@ function ComposerWithSuggestions({
multiline
ref={setTextInputRef}
placeholder={inputPlaceholder}
- placeholderTextColor={themeColors.placeholderText}
+ placeholderTextColor={theme.placeholderText}
onChangeText={(commentValue) => updateComment(commentValue, true)}
onKeyPress={triggerHotkeyActions}
textAlignVertical="top"
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js
index c0a1151f0202..7bce37dc3826 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js
@@ -29,7 +29,7 @@ import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
import ReportDropUI from '@pages/home/report/ReportDropUI';
import ReportTypingIndicator from '@pages/home/report/ReportTypingIndicator';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as EmojiPickerActions from '@userActions/EmojiPickerAction';
import * as Report from '@userActions/Report';
import * as User from '@userActions/User';
@@ -116,6 +116,7 @@ function ReportActionCompose({
shouldShowComposeInput,
isReportReadyForDisplay,
}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const {isMediumScreenWidth, isSmallScreenWidth} = useWindowDimensions();
const animatedRef = useAnimatedRef();
diff --git a/src/pages/home/report/ReportActionCompose/SendButton.js b/src/pages/home/report/ReportActionCompose/SendButton.js
index 41f35b0f8d3d..60c657ca95c7 100644
--- a/src/pages/home/report/ReportActionCompose/SendButton.js
+++ b/src/pages/home/report/ReportActionCompose/SendButton.js
@@ -7,8 +7,8 @@ import * as Expensicons from '@components/Icon/Expensicons';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import Tooltip from '@components/Tooltip';
import useLocalize from '@hooks/useLocalize';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -20,6 +20,8 @@ const propTypes = {
};
function SendButton({isDisabled: isDisabledProp, handleSendMessage}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const Tap = Gesture.Tap()
@@ -48,7 +50,7 @@ function SendButton({isDisabled: isDisabledProp, handleSendMessage}) {
{({pressed}) => (
)}
diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.js b/src/pages/home/report/ReportActionCompose/SuggestionMention.js
index 1b93b2d56af0..0a9ed2c11293 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionMention.js
+++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.js
@@ -146,7 +146,7 @@ function SuggestionMention({
const filteredPersonalDetails = _.filter(_.values(personalDetailsParam), (detail) => {
// If we don't have user's primary login, that member is not known to the current user and hence we do not allow them to be mentioned
- if (!detail.login) {
+ if (!detail.login || detail.isOptimisticPersonalDetail) {
return false;
}
if (searchValue && !`${detail.displayName} ${detail.login}`.toLowerCase().includes(searchValue.toLowerCase())) {
diff --git a/src/pages/home/report/ReportActionItemBasicMessage.js b/src/pages/home/report/ReportActionItemBasicMessage.js
index 87997b29b833..219564e5a8ec 100644
--- a/src/pages/home/report/ReportActionItemBasicMessage.js
+++ b/src/pages/home/report/ReportActionItemBasicMessage.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import Text from '@components/Text';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
const propTypes = {
message: PropTypes.string.isRequired,
@@ -14,6 +14,7 @@ const defaultProps = {
};
function ReportActionItemBasicMessage(props) {
+ const styles = useThemeStyles();
return (
{props.message}
diff --git a/src/pages/home/report/ReportActionItemCreated.js b/src/pages/home/report/ReportActionItemCreated.js
index a7772ad5e0fb..4c7f14a21abc 100644
--- a/src/pages/home/report/ReportActionItemCreated.js
+++ b/src/pages/home/report/ReportActionItemCreated.js
@@ -14,8 +14,8 @@ import compose from '@libs/compose';
import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector';
import * as ReportUtils from '@libs/ReportUtils';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -49,6 +49,7 @@ const defaultProps = {
};
function ReportActionItemCreated(props) {
+ const styles = useThemeStyles();
if (!ReportUtils.isChatReport(props.report)) {
return null;
}
diff --git a/src/pages/home/report/ReportActionItemDate.js b/src/pages/home/report/ReportActionItemDate.js
index 346a1c2683f0..a65bdd4aa7d7 100644
--- a/src/pages/home/report/ReportActionItemDate.js
+++ b/src/pages/home/report/ReportActionItemDate.js
@@ -4,7 +4,7 @@ import {withCurrentDate} from '@components/OnyxProvider';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import compose from '@libs/compose';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
const propTypes = {
/** UTC timestamp for when the action was created */
@@ -13,6 +13,7 @@ const propTypes = {
};
function ReportActionItemDate(props) {
+ const styles = useThemeStyles();
return {props.datetimeToCalendarTime(props.created)};
}
diff --git a/src/pages/home/report/ReportActionItemDraft.js b/src/pages/home/report/ReportActionItemDraft.js
index 500c4146e608..9b3839aa78f2 100644
--- a/src/pages/home/report/ReportActionItemDraft.js
+++ b/src/pages/home/report/ReportActionItemDraft.js
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
const propTypes = {
/** Children view component for this action item */
@@ -9,6 +9,7 @@ const propTypes = {
};
function ReportActionItemDraft(props) {
+ const styles = useThemeStyles();
return (
{props.children}
diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js
index ab813db2f2e9..c5097bdfc4a5 100644
--- a/src/pages/home/report/ReportActionItemFragment.js
+++ b/src/pages/home/report/ReportActionItemFragment.js
@@ -1,4 +1,3 @@
-import Str from 'expensify-common/lib/str';
import PropTypes from 'prop-types';
import React, {memo} from 'react';
import avatarPropTypes from '@components/avatarPropTypes';
@@ -8,16 +7,13 @@ import Text from '@components/Text';
import UserDetailsTooltip from '@components/UserDetailsTooltip';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
-import ZeroWidthView from '@components/ZeroWidthView';
import compose from '@libs/compose';
import convertToLTR from '@libs/convertToLTR';
-import * as DeviceCapabilities from '@libs/DeviceCapabilities';
-import * as EmojiUtils from '@libs/EmojiUtils';
-import editedLabelStyles from '@styles/editedLabelStyles';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
-import variables from '@styles/variables';
+import * as ReportUtils from '@libs/ReportUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
+import AttachmentCommentFragment from './comment/AttachmentCommentFragment';
+import TextCommentFragment from './comment/TextCommentFragment';
import reportActionFragmentPropTypes from './reportActionFragmentPropTypes';
const propTypes = {
@@ -63,6 +59,9 @@ const propTypes = {
/** Whether the comment is a thread parent message/the first message in a thread */
isThreadParentMessage: PropTypes.bool,
+ /** Should the comment have the appearance of being grouped with the previous comment? */
+ displayAsGroup: PropTypes.bool,
+
/** Whether the report action type is 'APPROVED' or 'SUBMITTED'. Used to style system messages from Old Dot */
isApprovedOrSubmittedReportAction: PropTypes.bool,
@@ -73,9 +72,6 @@ const propTypes = {
/** localization props */
...withLocalizePropTypes,
-
- /** Should the comment have the appearance of being grouped with the previous comment? */
- displayAsGroup: PropTypes.bool,
};
const defaultProps = {
@@ -98,70 +94,40 @@ const defaultProps = {
};
function ReportActionItemFragment(props) {
- switch (props.fragment.type) {
+ const styles = useThemeStyles();
+ const fragment = props.fragment;
+
+ switch (fragment.type) {
case 'COMMENT': {
- const {html, text} = props.fragment;
- const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && props.network.isOffline;
+ const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
// Threaded messages display "[Deleted message]" instead of being hidden altogether.
// While offline we display the previous message with a strikethrough style. Once online we want to
// immediately display "[Deleted message]" while the delete action is pending.
- if ((!props.network.isOffline && props.isThreadParentMessage && props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) || props.fragment.isDeletedParentAction) {
+ if ((!props.network.isOffline && props.isThreadParentMessage && isPendingDelete) || props.fragment.isDeletedParentAction) {
return ${props.translate('parentReportAction.deletedMessage')}`} />;
}
- // If the only difference between fragment.text and fragment.html is
tags
- // we render it as text, not as html.
- // This is done to render emojis with line breaks between them as text.
- const differByLineBreaksOnly = Str.replaceAll(html, '
', '\n') === text;
-
- // Only render HTML if we have html in the fragment
- if (!differByLineBreaksOnly) {
- const editedTag = props.fragment.isEdited ? `` : '';
- const htmlContent = isPendingDelete ? `${html}` : html;
-
- const htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent;
-
- return ${htmlWithTag}` : `${htmlWithTag}`} />;
+ if (ReportUtils.isReportMessageAttachment(fragment)) {
+ return (
+
+ );
}
- const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text);
return (
-
-
-
- {convertToLTR(props.iouMessage || text)}
-
- {Boolean(props.fragment.isEdited) && (
- <>
-
- {' '}
-
-
- {props.translate('reportActionCompose.edited')}
-
- >
- )}
-
+
);
}
case 'TEXT': {
@@ -182,7 +148,7 @@ function ReportActionItemFragment(props) {
numberOfLines={props.isSingleLine ? 1 : undefined}
style={[styles.chatItemMessageHeaderSender, props.isSingleLine ? styles.pre : styles.preWrap]}
>
- {props.fragment.text}
+ {fragment.text}
);
diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js
index 4c6603c052a3..2265530f29a1 100644
--- a/src/pages/home/report/ReportActionItemMessage.js
+++ b/src/pages/home/report/ReportActionItemMessage.js
@@ -6,7 +6,7 @@ import _ from 'underscore';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ReportActionItemFragment from './ReportActionItemFragment';
import reportActionPropTypes from './reportActionPropTypes';
@@ -37,8 +37,8 @@ const defaultProps = {
};
function ReportActionItemMessage(props) {
- const messages = _.compact(props.action.previousMessage || props.action.message);
- const isAttachment = ReportUtils.isReportMessageAttachment(_.last(messages));
+ const styles = useThemeStyles();
+ const fragments = _.compact(props.action.previousMessage || props.action.message);
const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action);
let iouMessage;
if (isIOUReport) {
@@ -56,7 +56,7 @@ function ReportActionItemMessage(props) {
* @returns {Object} report action item fragments
*/
const renderReportActionItemFragments = (shouldWrapInText) => {
- const reportActionItemFragments = _.map(messages, (fragment, index) => (
+ const reportActionItemFragments = _.map(fragments, (fragment, index) => (
+
{!props.isHidden ? (
renderReportActionItemFragments(isApprovedOrSubmittedReportAction)
) : (
diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js
index 60714dad9864..b723ddd93582 100644
--- a/src/pages/home/report/ReportActionItemMessageEdit.js
+++ b/src/pages/home/report/ReportActionItemMessageEdit.js
@@ -28,8 +28,8 @@ import * as ReportUtils from '@libs/ReportUtils';
import setShouldShowComposeInputKeyboardAware from '@libs/setShouldShowComposeInputKeyboardAware';
import reportPropTypes from '@pages/reportPropTypes';
import containerComposeStyles from '@styles/containerComposeStyles';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import * as EmojiPickerAction from '@userActions/EmojiPickerAction';
import * as InputFocus from '@userActions/InputFocus';
import * as Report from '@userActions/Report';
@@ -80,6 +80,8 @@ const messageEditInput = 'messageEditInput';
const isMobileSafari = Browser.isMobileSafari();
function ReportActionItemMessageEdit(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const reportScrollManager = useReportScrollManager();
const {translate, preferredLocale} = useLocalize();
const {isKeyboardShown} = useKeyboardState();
@@ -457,7 +459,7 @@ function ReportActionItemMessageEdit(props) {
>
diff --git a/src/pages/home/report/ReportActionItemParentAction.js b/src/pages/home/report/ReportActionItemParentAction.js
index e2274ce41ae0..ad319b86e634 100644
--- a/src/pages/home/report/ReportActionItemParentAction.js
+++ b/src/pages/home/report/ReportActionItemParentAction.js
@@ -9,8 +9,8 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW
import compose from '@libs/compose';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Report from '@userActions/Report';
import ONYXKEYS from '@src/ONYXKEYS';
import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground';
@@ -46,6 +46,7 @@ const defaultProps = {
};
function ReportActionItemParentAction(props) {
+ const styles = useThemeStyles();
const parentReportAction = props.parentReportActions[`${props.report.parentReportActionID}`];
// In case of transaction threads, we do not want to render the parent report action.
diff --git a/src/pages/home/report/ReportActionItemThread.js b/src/pages/home/report/ReportActionItemThread.js
index 9cfc46f4bf91..bfb5ae0c5a44 100644
--- a/src/pages/home/report/ReportActionItemThread.js
+++ b/src/pages/home/report/ReportActionItemThread.js
@@ -7,7 +7,7 @@ import PressableWithSecondaryInteraction from '@components/PressableWithSecondar
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
import compose from '@libs/compose';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
@@ -35,6 +35,7 @@ const propTypes = {
};
function ReportActionItemThread(props) {
+ const styles = useThemeStyles();
const numberOfRepliesText = props.numberOfReplies > CONST.MAX_THREAD_REPLIES_PREVIEW ? `${CONST.MAX_THREAD_REPLIES_PREVIEW}+` : `${props.numberOfReplies}`;
const replyText = props.numberOfReplies === 1 ? props.translate('threads.reply') : props.translate('threads.replies');
diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
index cc19170ae318..dd537959c91f 100644
--- a/src/pages/home/report/ReportActionsList.js
+++ b/src/pages/home/report/ReportActionsList.js
@@ -2,6 +2,7 @@ import {useRoute} from '@react-navigation/native';
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
+import {DeviceEventEmitter} from 'react-native';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import _ from 'underscore';
import InvertedFlatList from '@components/InvertedFlatList';
@@ -16,7 +17,7 @@ import DateUtils from '@libs/DateUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
@@ -82,16 +83,23 @@ const defaultProps = {
const VERTICAL_OFFSET_THRESHOLD = 200;
const MSG_VISIBLE_THRESHOLD = 250;
-// Seems that there is an architecture issue that prevents us from using the reportID with useRef
-// the useRef value gets reset when the reportID changes, so we use a global variable to keep track
-let prevReportID = null;
-
// In the component we are subscribing to the arrival of new actions.
// As there is the possibility that there are multiple instances of a ReportScreen
// for the same report, we only ever want one subscription to be active, as
// the subscriptions could otherwise be conflicting.
const newActionUnsubscribeMap = {};
+// Caching the reportID and reportActionID for unread markers ensures persistent tracking
+// across multiple reports, preserving the green line placement and allowing retrieval
+// of the relevant reportActionID for displaying the green line.
+// We need to persist it across reports because there are at least 3 ReportScreen components created so the
+// internal states are resetted or recreated.
+const cacheUnreadMarkers = new Map();
+
+// Seems that there is an architecture issue that prevents us from using the reportID with useRef
+// the useRef value gets reset when the reportID changes, so we use a global variable to keep track
+let prevReportID = null;
+
/**
* Create a unique key for each action in the FlatList.
* We use the reportActionID that is a string representation of a random 64-bit int, which should be
@@ -130,18 +138,28 @@ function ReportActionsList({
onLayout,
isComposerFullSize,
}) {
+ const styles = useThemeStyles();
const reportScrollManager = useReportScrollManager();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const route = useRoute();
const opacity = useSharedValue(0);
const userActiveSince = useRef(null);
- const [currentUnreadMarker, setCurrentUnreadMarker] = useState(null);
+ const unreadActionSubscription = useRef(null);
+ const markerInit = () => {
+ if (!cacheUnreadMarkers.has(report.reportID)) {
+ return null;
+ }
+ return cacheUnreadMarkers.get(report.reportID);
+ };
+ const [currentUnreadMarker, setCurrentUnreadMarker] = useState(markerInit);
const scrollingVerticalOffset = useRef(0);
const readActionSkipped = useRef(false);
const hasHeaderRendered = useRef(false);
const hasFooterRendered = useRef(false);
const reportActionSize = useRef(sortedReportActions.length);
+ const lastReadTimeRef = useRef(report.lastReadTime);
+
const linkedReportActionID = lodashGet(route, 'params.reportActionID', '');
// This state is used to force a re-render when the user manually marks a message as unread
@@ -185,25 +203,41 @@ function ReportActionsList({
return;
}
+ cacheUnreadMarkers.delete(report.reportID);
reportActionSize.current = sortedReportActions.length;
setCurrentUnreadMarker(null);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sortedReportActions.length, report.reportID]);
useEffect(() => {
- const didManuallyMarkReportAsUnread = report.lastReadTime < DateUtils.getDBTime() && ReportUtils.isUnread(report);
- if (didManuallyMarkReportAsUnread) {
- // Clearing the current unread marker so that it can be recalculated
- setCurrentUnreadMarker(null);
- setMessageManuallyMarkedUnread(new Date().getTime());
+ if (!userActiveSince.current || report.reportID !== prevReportID) {
return;
}
-
+ if (!messageManuallyMarkedUnread && lastReadTimeRef.current && lastReadTimeRef.current < report.lastReadTime) {
+ cacheUnreadMarkers.delete(report.reportID);
+ }
+ lastReadTimeRef.current = report.lastReadTime;
setMessageManuallyMarkedUnread(0);
- // We only care when a new lastReadTime is set in the report
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [report.lastReadTime]);
+ }, [report.lastReadTime, report.reportID]);
+
+ useEffect(() => {
+ // If the reportID changes, we reset the userActiveSince to null, we need to do it because
+ // this component doesn't unmount when the reportID changes
+ if (unreadActionSubscription.current) {
+ unreadActionSubscription.current.remove();
+ unreadActionSubscription.current = null;
+ }
+
+ // Listen to specific reportID for unread event and set the marker to new message
+ unreadActionSubscription.current = DeviceEventEmitter.addListener(`unreadAction_${report.reportID}`, (newLastReadTime) => {
+ cacheUnreadMarkers.delete(report.reportID);
+ lastReadTimeRef.current = newLastReadTime;
+ setCurrentUnreadMarker(null);
+ setMessageManuallyMarkedUnread(new Date().getTime());
+ });
+ }, [report.reportID]);
useEffect(() => {
// Why are we doing this, when in the cleanup of the useEffect we are already calling the unsubscribe function?
@@ -281,7 +315,7 @@ function ReportActionsList({
const minimumReportActionHeight = styles.chatItem.paddingTop + styles.chatItem.paddingBottom + variables.fontSizeNormalHeight;
const availableHeight = windowHeight - (CONST.CHAT_FOOTER_MIN_HEIGHT + variables.contentHeaderHeight);
return Math.ceil(availableHeight / minimumReportActionHeight);
- }, [windowHeight]);
+ }, [styles.chatItem.paddingBottom, styles.chatItem.paddingTop, windowHeight]);
/**
* Thread's divider line should hide when the first chat in the thread is marked as unread.
@@ -302,17 +336,21 @@ function ReportActionsList({
let shouldDisplay = false;
if (!currentUnreadMarker) {
const nextMessage = sortedReportActions[index + 1];
- const isCurrentMessageUnread = isMessageUnread(reportAction, report.lastReadTime);
- shouldDisplay = isCurrentMessageUnread && (!nextMessage || !isMessageUnread(nextMessage, report.lastReadTime));
+ const isCurrentMessageUnread = isMessageUnread(reportAction, lastReadTimeRef.current);
+ shouldDisplay = isCurrentMessageUnread && (!nextMessage || !isMessageUnread(nextMessage, lastReadTimeRef.current));
if (!messageManuallyMarkedUnread) {
shouldDisplay = shouldDisplay && reportAction.actorAccountID !== Report.getCurrentUserAccountID();
}
+ if (shouldDisplay) {
+ cacheUnreadMarkers.set(report.reportID, reportAction.reportActionID);
+ }
} else {
shouldDisplay = reportAction.reportActionID === currentUnreadMarker;
}
+
return shouldDisplay;
},
- [currentUnreadMarker, sortedReportActions, report.lastReadTime, messageManuallyMarkedUnread],
+ [currentUnreadMarker, sortedReportActions, report.reportID, messageManuallyMarkedUnread],
);
useEffect(() => {
@@ -326,13 +364,14 @@ function ReportActionsList({
}
markerFound = true;
if (!currentUnreadMarker && currentUnreadMarker !== reportAction.reportActionID) {
+ cacheUnreadMarkers.set(report.reportID, reportAction.reportActionID);
setCurrentUnreadMarker(reportAction.reportActionID);
}
});
if (!markerFound) {
setCurrentUnreadMarker(null);
}
- }, [sortedReportActions, report.lastReadTime, messageManuallyMarkedUnread, shouldDisplayNewMarker, currentUnreadMarker]);
+ }, [sortedReportActions, report.lastReadTime, report.reportID, messageManuallyMarkedUnread, shouldDisplayNewMarker, currentUnreadMarker]);
const renderItem = useCallback(
({item: reportAction, index}) => (
@@ -359,7 +398,7 @@ function ReportActionsList({
const contentContainerStyle = useMemo(
() => [styles.chatContentScrollView, isLoadingNewerReportActions ? styles.chatContentScrollViewWithHeaderLoader : {}],
- [isLoadingNewerReportActions],
+ [isLoadingNewerReportActions, styles.chatContentScrollView, styles.chatContentScrollViewWithHeaderLoader],
);
const lastReportAction = useMemo(() => _.last(sortedReportActions) || {}, [sortedReportActions]);
diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js
index 01ec967d76b1..b4d21de919bf 100755
--- a/src/pages/home/report/ReportActionsView.js
+++ b/src/pages/home/report/ReportActionsView.js
@@ -185,7 +185,7 @@ function ReportActionsView(props) {
const oldestReportAction = _.last(props.reportActions);
// Don't load more chats if we're already at the beginning of the chat history
- if (oldestReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) {
+ if (!oldestReportAction || oldestReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) {
return;
}
// Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments
diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js
index ae0c941b37e7..2f4c81313ec2 100644
--- a/src/pages/home/report/ReportDropUI.js
+++ b/src/pages/home/report/ReportDropUI.js
@@ -6,7 +6,7 @@ import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
const propTypes = {
/** Callback to execute when a file is dropped. */
@@ -14,6 +14,7 @@ const propTypes = {
};
function ReportDropUI({onDrop}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
return (
diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js
index 00d586b67697..e5dd5da19ad5 100644
--- a/src/pages/home/report/ReportFooter.js
+++ b/src/pages/home/report/ReportFooter.js
@@ -12,7 +12,7 @@ import useNetwork from '@hooks/useNetwork';
import compose from '@libs/compose';
import * as ReportUtils from '@libs/ReportUtils';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
@@ -64,6 +64,7 @@ const defaultProps = {
};
function ReportFooter(props) {
+ const styles = useThemeStyles();
const {isOffline} = useNetwork();
const chatFooterStyles = {...styles.chatFooter, minHeight: !isOffline ? CONST.CHAT_FOOTER_MIN_HEIGHT : 0};
const isArchivedRoom = ReportUtils.isArchivedRoom(props.report);
diff --git a/src/pages/home/report/ReportTypingIndicator.js b/src/pages/home/report/ReportTypingIndicator.js
index 7b2b46b7d097..3a2c611ac358 100755
--- a/src/pages/home/report/ReportTypingIndicator.js
+++ b/src/pages/home/report/ReportTypingIndicator.js
@@ -8,7 +8,7 @@ import Text from '@components/Text';
import TextWithEllipsis from '@components/TextWithEllipsis';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import compose from '@libs/compose';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as PersonalDetails from '@userActions/PersonalDetails';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -27,6 +27,7 @@ const defaultProps = {
};
function ReportTypingIndicator(props) {
+ const styles = useThemeStyles();
const usersTyping = useMemo(() => _.filter(_.keys(props.userTypingStatuses), (loginOrAccountID) => props.userTypingStatuses[loginOrAccountID]), [props.userTypingStatuses]);
// If we are offline, the user typing statuses are not up-to-date so do not show them
if (props.network.isOffline) {
diff --git a/src/pages/home/report/comment/AttachmentCommentFragment.js b/src/pages/home/report/comment/AttachmentCommentFragment.js
new file mode 100644
index 000000000000..ec75edd18a35
--- /dev/null
+++ b/src/pages/home/report/comment/AttachmentCommentFragment.js
@@ -0,0 +1,34 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import {View} from 'react-native';
+import reportActionSourcePropType from '@pages/home/report/reportActionSourcePropType';
+import useThemeStyles from '@styles/useThemeStyles';
+import RenderCommentHTML from './RenderCommentHTML';
+
+const propTypes = {
+ /** The reportAction's source */
+ source: reportActionSourcePropType.isRequired,
+
+ /** The message fragment's HTML */
+ html: PropTypes.string.isRequired,
+
+ /** Should extra margin be added on top of the component? */
+ addExtraMargin: PropTypes.bool.isRequired,
+};
+
+function AttachmentCommentFragment({addExtraMargin, html, source}) {
+ const styles = useThemeStyles();
+ return (
+
+
+
+ );
+}
+
+AttachmentCommentFragment.propTypes = propTypes;
+AttachmentCommentFragment.displayName = 'AttachmentCommentFragment';
+
+export default AttachmentCommentFragment;
diff --git a/src/pages/home/report/comment/RenderCommentHTML.js b/src/pages/home/report/comment/RenderCommentHTML.js
new file mode 100644
index 000000000000..14039af21189
--- /dev/null
+++ b/src/pages/home/report/comment/RenderCommentHTML.js
@@ -0,0 +1,23 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import RenderHTML from '@components/RenderHTML';
+import reportActionSourcePropType from '@pages/home/report/reportActionSourcePropType';
+
+const propTypes = {
+ /** The reportAction's source */
+ source: reportActionSourcePropType.isRequired,
+
+ /** The comment's HTML */
+ html: PropTypes.string.isRequired,
+};
+
+function RenderCommentHTML({html, source}) {
+ const commentHtml = source === 'email' ? `${html}` : `${html}`;
+
+ return ;
+}
+
+RenderCommentHTML.propTypes = propTypes;
+RenderCommentHTML.displayName = 'RenderCommentHTML';
+
+export default RenderCommentHTML;
diff --git a/src/pages/home/report/comment/TextCommentFragment.js b/src/pages/home/report/comment/TextCommentFragment.js
new file mode 100644
index 000000000000..403c470dfc9d
--- /dev/null
+++ b/src/pages/home/report/comment/TextCommentFragment.js
@@ -0,0 +1,120 @@
+import Str from 'expensify-common/lib/str';
+import PropTypes from 'prop-types';
+import React, {memo} from 'react';
+import Text from '@components/Text';
+import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
+import ZeroWidthView from '@components/ZeroWidthView';
+import compose from '@libs/compose';
+import convertToLTR from '@libs/convertToLTR';
+import * as DeviceCapabilities from '@libs/DeviceCapabilities';
+import * as EmojiUtils from '@libs/EmojiUtils';
+import reportActionFragmentPropTypes from '@pages/home/report/reportActionFragmentPropTypes';
+import reportActionSourcePropType from '@pages/home/report/reportActionSourcePropType';
+import editedLabelStyles from '@styles/editedLabelStyles';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+import RenderCommentHTML from './RenderCommentHTML';
+
+const propTypes = {
+ /** The reportAction's source */
+ source: reportActionSourcePropType.isRequired,
+
+ /** The message fragment needing to be displayed */
+ fragment: reportActionFragmentPropTypes.isRequired,
+
+ /** Should this message fragment be styled as deleted? */
+ styleAsDeleted: PropTypes.bool.isRequired,
+
+ /** Text of an IOU report action */
+ iouMessage: PropTypes.string,
+
+ /** Should the comment have the appearance of being grouped with the previous comment? */
+ displayAsGroup: PropTypes.bool.isRequired,
+
+ /** Additional styles to add after local styles. */
+ style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]).isRequired,
+
+ ...windowDimensionsPropTypes,
+
+ /** localization props */
+ ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+ iouMessage: undefined,
+};
+
+function TextCommentFragment(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
+ const {fragment, styleAsDeleted} = props;
+ const {html, text} = fragment;
+
+ // If the only difference between fragment.text and fragment.html is
tags
+ // we render it as text, not as html.
+ // This is done to render emojis with line breaks between them as text.
+ const differByLineBreaksOnly = Str.replaceAll(html, '
', '\n') === text;
+
+ // Only render HTML if we have html in the fragment
+ if (!differByLineBreaksOnly) {
+ const editedTag = fragment.isEdited ? `` : '';
+ const htmlContent = styleAsDeleted ? `${html}` : html;
+
+ const htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent;
+
+ return (
+
+ );
+ }
+
+ const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text);
+
+ return (
+
+
+
+ {convertToLTR(props.iouMessage || text)}
+
+ {Boolean(fragment.isEdited) && (
+ <>
+
+ {' '}
+
+
+ {props.translate('reportActionCompose.edited')}
+
+ >
+ )}
+
+ );
+}
+
+TextCommentFragment.propTypes = propTypes;
+TextCommentFragment.defaultProps = defaultProps;
+TextCommentFragment.displayName = 'TextCommentFragment';
+
+export default compose(withWindowDimensions, withLocalize)(memo(TextCommentFragment));
diff --git a/src/pages/home/report/reportActionSourcePropType.js b/src/pages/home/report/reportActionSourcePropType.js
new file mode 100644
index 000000000000..0ad9662eb693
--- /dev/null
+++ b/src/pages/home/report/reportActionSourcePropType.js
@@ -0,0 +1,3 @@
+import PropTypes from 'prop-types';
+
+export default PropTypes.oneOf(['Chronos', 'email', 'ios', 'android', 'web', 'email', '']);
diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.js b/src/pages/home/sidebar/AvatarWithOptionalStatus.js
index 300a898b9e90..4507b21a382c 100644
--- a/src/pages/home/sidebar/AvatarWithOptionalStatus.js
+++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.js
@@ -6,7 +6,7 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import PressableAvatarWithIndicator from './PressableAvatarWithIndicator';
@@ -25,6 +25,7 @@ const defaultProps = {
};
function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const showStatusPage = useCallback(() => {
diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js
index ad981a190a70..5e69be266342 100644
--- a/src/pages/home/sidebar/SidebarLinks.js
+++ b/src/pages/home/sidebar/SidebarLinks.js
@@ -19,9 +19,9 @@ import onyxSubscribe from '@libs/onyxSubscribe';
import SidebarUtils from '@libs/SidebarUtils';
import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import safeAreaInsetPropTypes from '@pages/safeAreaInsetPropTypes';
-import styles from '@styles/styles';
import * as StyleUtils from '@styles/StyleUtils';
-import defaultTheme from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import variables from '@styles/variables';
import * as App from '@userActions/App';
import * as Session from '@userActions/Session';
@@ -52,6 +52,8 @@ const propTypes = {
};
function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const modal = useRef({});
const {translate, updateLocale} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
@@ -155,7 +157,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority
diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js
index 9cf981170ee5..dc7d00566bc0 100644
--- a/src/pages/home/sidebar/SidebarLinksData.js
+++ b/src/pages/home/sidebar/SidebarLinksData.js
@@ -12,7 +12,7 @@ import compose from '@libs/compose';
import * as SessionUtils from '@libs/SessionUtils';
import SidebarUtils from '@libs/SidebarUtils';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import SidebarLinks, {basePropTypes} from './SidebarLinks';
@@ -64,6 +64,7 @@ const defaultProps = {
};
function SidebarLinksData({isFocused, allReportActions, betas, chatReports, currentReportID, insets, isLoadingReportData, onLinkClick, policies, priorityMode}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const reportIDsRef = useRef(null);
diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
index 0d2930220bcd..5b7a126a4655 100644
--- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
+++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
@@ -4,7 +4,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import * as Browser from '@libs/Browser';
import Performance from '@libs/Performance';
import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import Timing from '@userActions/Timing';
import CONST from '@src/CONST';
import sidebarPropTypes from './sidebarPropTypes';
@@ -18,6 +18,7 @@ const startTimer = () => {
};
function BaseSidebarScreen(props) {
+ const styles = useThemeStyles();
useEffect(() => {
Performance.markStart(CONST.TIMING.SIDEBAR_LOADED);
Timing.start(CONST.TIMING.SIDEBAR_LOADED, true);
diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
index 57f31a8c3e9f..739f7e3e0295 100644
--- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
+++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js
@@ -14,7 +14,7 @@ import usePrevious from '@hooks/usePrevious';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import Permissions from '@libs/Permissions';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as App from '@userActions/App';
import * as IOU from '@userActions/IOU';
import * as Policy from '@userActions/Policy';
@@ -87,6 +87,7 @@ const defaultProps = {
* @returns {JSX.Element}
*/
function FloatingActionButtonAndPopover(props) {
+ const styles = useThemeStyles();
const [isCreateMenuActive, setIsCreateMenuActive] = useState(false);
const isAnonymousUser = Session.isAnonymousUser();
const anchorRef = useRef(null);
diff --git a/src/pages/home/sidebar/SignInButton.js b/src/pages/home/sidebar/SignInButton.js
index afa67bdc04cd..c42e56551200 100644
--- a/src/pages/home/sidebar/SignInButton.js
+++ b/src/pages/home/sidebar/SignInButton.js
@@ -4,11 +4,12 @@ import {View} from 'react-native';
import Button from '@components/Button';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import useLocalize from '@hooks/useLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
function SignInButton() {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
return (
diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js
index 20344a08a2c8..eab9ab5a7510 100644
--- a/src/pages/iou/IOUCurrencySelection.js
+++ b/src/pages/iou/IOUCurrencySelection.js
@@ -157,20 +157,29 @@ function IOUCurrencySelection(props) {
onEntryTransitionEnd={() => optionsSelectorRef.current && optionsSelectorRef.current.focus()}
testID={IOUCurrencySelection.displayName}
>
- Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID))}
- />
-
+ {({didScreenTransitionEnd}) => (
+ <>
+ Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID))}
+ />
+ {
+ if (!didScreenTransitionEnd) {
+ return;
+ }
+ confirmCurrencySelection(option);
+ }}
+ headerMessage={headerMessage}
+ initiallyFocusedOptionKey={initiallyFocusedOptionKey}
+ showScrollIndicator
+ />
+ >
+ )}
);
}
diff --git a/src/pages/iou/MoneyRequestCategoryPage.js b/src/pages/iou/MoneyRequestCategoryPage.js
index 9551004eb1ab..7f3bf7985ba6 100644
--- a/src/pages/iou/MoneyRequestCategoryPage.js
+++ b/src/pages/iou/MoneyRequestCategoryPage.js
@@ -10,7 +10,7 @@ import useLocalize from '@hooks/useLocalize';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -43,6 +43,7 @@ const defaultProps = {
};
function MoneyRequestCategoryPage({route, report, iou}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const reportID = lodashGet(route, 'params.reportID', '');
diff --git a/src/pages/iou/MoneyRequestDatePage.js b/src/pages/iou/MoneyRequestDatePage.js
index 430af5d9de94..2eae14b2e180 100644
--- a/src/pages/iou/MoneyRequestDatePage.js
+++ b/src/pages/iou/MoneyRequestDatePage.js
@@ -10,7 +10,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -49,6 +49,7 @@ const defaultProps = {
};
function MoneyRequestDatePage({iou, route, selectedTab}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const iouType = lodashGet(route, 'params.iouType', '');
const reportID = lodashGet(route, 'params.reportID', '');
diff --git a/src/pages/iou/MoneyRequestDescriptionPage.js b/src/pages/iou/MoneyRequestDescriptionPage.js
index 35fcc0d900e9..43d7b58bfc7f 100644
--- a/src/pages/iou/MoneyRequestDescriptionPage.js
+++ b/src/pages/iou/MoneyRequestDescriptionPage.js
@@ -16,7 +16,7 @@ import * as Browser from '@libs/Browser';
import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import Navigation from '@libs/Navigation/Navigation';
import updateMultilineInputRange from '@libs/UpdateMultilineInputRange';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -55,6 +55,7 @@ const defaultProps = {
};
function MoneyRequestDescriptionPage({iou, route, selectedTab}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const inputRef = useRef(null);
const focusTimeoutRef = useRef(null);
diff --git a/src/pages/iou/MoneyRequestMerchantPage.js b/src/pages/iou/MoneyRequestMerchantPage.js
index ce9fc3be7d34..2c5869dfa7a3 100644
--- a/src/pages/iou/MoneyRequestMerchantPage.js
+++ b/src/pages/iou/MoneyRequestMerchantPage.js
@@ -12,7 +12,7 @@ import TextInput from '@components/TextInput';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -48,6 +48,7 @@ const defaultProps = {
};
function MoneyRequestMerchantPage({iou, route}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const {inputCallbackRef} = useAutoFocusInput();
const iouType = lodashGet(route, 'params.iouType', '');
diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js
index e6ea81fc896b..37eaee513fb6 100644
--- a/src/pages/iou/MoneyRequestSelectorPage.js
+++ b/src/pages/iou/MoneyRequestSelectorPage.js
@@ -19,7 +19,7 @@ import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator';
import * as ReportUtils from '@libs/ReportUtils';
import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -57,6 +57,7 @@ const defaultProps = {
};
function MoneyRequestSelectorPage(props) {
+ const styles = useThemeStyles();
const [isDraggingOver, setIsDraggingOver] = useState(false);
const iouType = lodashGet(props.route, 'params.iouType', '');
diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js
index 43f2ebde10ba..b8ef1dba6207 100644
--- a/src/pages/iou/MoneyRequestTagPage.js
+++ b/src/pages/iou/MoneyRequestTagPage.js
@@ -13,7 +13,7 @@ import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -50,6 +50,7 @@ const defaultProps = {
};
function MoneyRequestTagPage({route, report, policyTags, iou}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const iouType = lodashGet(route, 'params.iouType', '');
diff --git a/src/pages/iou/ReceiptDropUI.js b/src/pages/iou/ReceiptDropUI.js
index 9c1483663d4b..18ad31e036e4 100644
--- a/src/pages/iou/ReceiptDropUI.js
+++ b/src/pages/iou/ReceiptDropUI.js
@@ -4,7 +4,7 @@ import {Text, View} from 'react-native';
import ReceiptUpload from '@assets/images/receipt-upload.svg';
import DragAndDropConsumer from '@components/DragAndDrop/Consumer';
import useLocalize from '@hooks/useLocalize';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -20,6 +20,7 @@ const defaultProps = {
};
function ReceiptDropUI({onDrop, receiptImageTopPosition}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
return (
diff --git a/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.js b/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.js
index e4b24f8a0ad8..10b16da13b6e 100644
--- a/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.js
+++ b/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.js
@@ -1,17 +1,20 @@
-import {useIsFocused} from '@react-navigation/native';
import PropTypes from 'prop-types';
import React, {useEffect, useRef} from 'react';
import {View} from 'react-native';
import Webcam from 'react-webcam';
+import useTabNavigatorFocus from '@hooks/useTabNavigatorFocus';
const propTypes = {
- /* Flag to turn on/off the torch/flashlight - if available */
+ /** Flag to turn on/off the torch/flashlight - if available */
torchOn: PropTypes.bool,
- /* Callback function when media stream becomes available - user granted camera permissions and camera starts to work */
+ /** The index of the tab that contains this camera */
+ cameraTabIndex: PropTypes.number.isRequired,
+
+ /** Callback function when media stream becomes available - user granted camera permissions and camera starts to work */
onUserMedia: PropTypes.func,
- /* Callback function passing torch/flashlight capability as bool param of the browser */
+ /** Callback function passing torch/flashlight capability as bool param of the browser */
onTorchAvailability: PropTypes.func,
};
@@ -22,9 +25,11 @@ const defaultProps = {
};
// Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused.
-const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, ...props}, ref) => {
+const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, cameraTabIndex, ...props}, ref) => {
const trackRef = useRef(null);
- const isCameraActive = useIsFocused();
+ const shouldShowCamera = useTabNavigatorFocus({
+ tabIndex: cameraTabIndex,
+ });
const handleOnUserMedia = (stream) => {
if (props.onUserMedia) {
@@ -51,7 +56,7 @@ const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, .
});
}, [torchOn]);
- if (!isCameraActive) {
+ if (!shouldShowCamera) {
return null;
}
return (
diff --git a/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.native.js b/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.native.js
index eca8042a6965..65c17d3cb7ab 100644
--- a/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.native.js
+++ b/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.native.js
@@ -1,77 +1,16 @@
-import {useNavigation} from '@react-navigation/native';
import PropTypes from 'prop-types';
-import React, {useEffect, useState} from 'react';
+import React from 'react';
import {Camera} from 'react-native-vision-camera';
-import withTabAnimation from '@components/withTabAnimation';
-import CONST from '@src/CONST';
+import useTabNavigatorFocus from '@hooks/useTabNavigatorFocus';
const propTypes = {
/* The index of the tab that contains this camera */
cameraTabIndex: PropTypes.number.isRequired,
-
- /* Whether we're in a tab navigator */
- isInTabNavigator: PropTypes.bool.isRequired,
-
- /** Name of the selected receipt tab */
- selectedTab: PropTypes.string.isRequired,
-
- /** The tab animation from hook */
- tabAnimation: PropTypes.shape({
- addListener: PropTypes.func,
- removeListener: PropTypes.func,
- }),
-};
-
-const defaultProps = {
- tabAnimation: undefined,
};
// Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused.
-const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, isInTabNavigator, selectedTab, tabAnimation, ...props}, ref) => {
- // Get navigation to get initial isFocused value (only needed once during init!)
- const navigation = useNavigation();
- const [isCameraActive, setIsCameraActive] = useState(() => navigation.isFocused());
-
- // Retrieve the animation value from the tab navigator, which ranges from 0 to the total number of pages displayed.
- // Even a minimal scroll towards the camera page (e.g., a value of 0.001 at start) should activate the camera for immediate responsiveness.
-
- useEffect(() => {
- if (!isInTabNavigator) {
- return;
- }
-
- const listenerId = tabAnimation.addListener(({value}) => {
- if (selectedTab !== CONST.TAB.SCAN) {
- return;
- }
- // Activate camera as soon the index is animating towards the `cameraTabIndex`
- setIsCameraActive(value > cameraTabIndex - 1 && value < cameraTabIndex + 1);
- });
-
- return () => {
- tabAnimation.removeListener(listenerId);
- };
- }, [cameraTabIndex, tabAnimation, isInTabNavigator, selectedTab]);
-
- // Note: The useEffect can be removed once VisionCamera V3 is used.
- // Its only needed for android, because there is a native cameraX android bug. With out this flow would break the camera:
- // 1. Open camera tab
- // 2. Take a picture
- // 3. Go back from the opened screen
- // 4. The camera is not working anymore
- useEffect(() => {
- const removeBlurListener = navigation.addListener('blur', () => {
- setIsCameraActive(false);
- });
- const removeFocusListener = navigation.addListener('focus', () => {
- setIsCameraActive(true);
- });
-
- return () => {
- removeBlurListener();
- removeFocusListener();
- };
- }, [navigation]);
+const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, ...props}, ref) => {
+ const isCameraActive = useTabNavigatorFocus({tabIndex: cameraTabIndex});
return (
!state, false);
- const [isTorchAvailable, setIsTorchAvailable] = useState(true);
+ const [isTorchAvailable, setIsTorchAvailable] = useState(false);
const cameraRef = useRef(null);
const hideReciptModal = () => {
@@ -175,9 +173,10 @@ function ReceiptSelector({route, transactionID, iou, report}) {
)}
+
{cameraPermissionState === 'denied' && (
@@ -221,7 +221,7 @@ function ReceiptSelector({route, transactionID, iou, report}) {
height={32}
width={32}
src={Expensicons.Gallery}
- fill={themeColors.textSupporting}
+ fill={theme.textSupporting}
/>
)}
@@ -248,7 +248,7 @@ function ReceiptSelector({route, transactionID, iou, report}) {
height={32}
width={32}
src={Expensicons.Bolt}
- fill={isFlashLightOn ? themeColors.iconHovered : themeColors.textSupporting}
+ fill={isFlashLightOn ? theme.iconHovered : theme.textSupporting}
/>
diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js
index 824c242cf02f..8a1db47aa742 100644
--- a/src/pages/iou/ReceiptSelector/index.native.js
+++ b/src/pages/iou/ReceiptSelector/index.native.js
@@ -18,8 +18,8 @@ import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -50,23 +50,17 @@ const propTypes = {
/** The id of the transaction we're editing */
transactionID: PropTypes.string,
-
- /** Whether or not the receipt selector is in a tab navigator for tab animations */
- isInTabNavigator: PropTypes.bool,
-
- /** Name of the selected receipt tab */
- selectedTab: PropTypes.string,
};
const defaultProps = {
report: {},
iou: iouDefaultProps,
transactionID: '',
- isInTabNavigator: true,
- selectedTab: '',
};
-function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator, selectedTab}) {
+function ReceiptSelector({route, report, iou, transactionID}) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const devices = useCameraDevices('wide-angle-camera');
const device = devices.back;
@@ -206,7 +200,7 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator, s
)}
@@ -218,8 +212,6 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator, s
zoom={device.neutralZoom}
photo
cameraTabIndex={pageIndex}
- isInTabNavigator={isInTabNavigator}
- selectedTab={selectedTab}
/>
)}
@@ -253,7 +245,7 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator, s
height={32}
width={32}
src={Expensicons.Gallery}
- fill={themeColors.textSupporting}
+ fill={theme.textSupporting}
/>
)}
@@ -280,7 +272,7 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator, s
height={32}
width={32}
src={Expensicons.Bolt}
- fill={flash ? themeColors.iconHovered : themeColors.textSupporting}
+ fill={flash ? theme.iconHovered : theme.textSupporting}
/>
diff --git a/src/pages/iou/SplitBillDetailsPage.js b/src/pages/iou/SplitBillDetailsPage.js
index e7be67ea5c8d..d1fe21d8cf4e 100644
--- a/src/pages/iou/SplitBillDetailsPage.js
+++ b/src/pages/iou/SplitBillDetailsPage.js
@@ -19,7 +19,7 @@ import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
import withReportAndReportActionOrNotFound from '@pages/home/report/withReportAndReportActionOrNotFound';
import personalDetailsPropType from '@pages/personalDetailsPropType';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -70,6 +70,7 @@ const defaultProps = {
};
function SplitBillDetailsPage(props) {
+ const styles = useThemeStyles();
const {reportID} = props.report;
const {translate} = useLocalize();
const reportAction = props.reportActions[`${props.route.params.reportActionID.toString()}`];
diff --git a/src/pages/iou/WaypointEditor.js b/src/pages/iou/WaypointEditor.js
index faddc988d250..ecc56fbae80f 100644
--- a/src/pages/iou/WaypointEditor.js
+++ b/src/pages/iou/WaypointEditor.js
@@ -19,7 +19,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as ValidationUtils from '@libs/ValidationUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Transaction from '@userActions/Transaction';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -77,6 +77,7 @@ const defaultProps = {
};
function WaypointEditor({route: {params: {iouType = '', transactionID = '', waypointIndex = '', threadReportID = 0}} = {}, transaction, recentWaypoints}) {
+ const styles = useThemeStyles();
const {windowWidth} = useWindowDimensions();
const [isDeleteStopModalOpen, setIsDeleteStopModalOpen] = useState(false);
const navigation = useNavigation();
diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js
index 734b7d9794b4..2150af0d1040 100644
--- a/src/pages/iou/steps/MoneyRequestAmountForm.js
+++ b/src/pages/iou/steps/MoneyRequestAmountForm.js
@@ -15,7 +15,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import getOperatingSystem from '@libs/getOperatingSystem';
import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
const propTypes = {
@@ -69,6 +69,7 @@ const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView';
const NUM_PAD_VIEW_ID = 'numPadView';
function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCurrencyButtonPress, onSubmitButtonPress, selectedTab}) {
+ const styles = useThemeStyles();
const {isExtraSmallScreenHeight} = useWindowDimensions();
const {translate, toLocaleDigit, numberFormat} = useLocalize();
@@ -108,16 +109,20 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu
}
};
+ const initializeAmount = useCallback((newAmount) => {
+ const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmount(newAmount).toString() : '';
+ setCurrentAmount(frontendAmount);
+ setSelection({
+ start: frontendAmount.length,
+ end: frontendAmount.length,
+ });
+ }, []);
+
useEffect(() => {
if (!currency || !_.isNumber(amount)) {
return;
}
- const amountAsStringForState = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : '';
- setCurrentAmount(amountAsStringForState);
- setSelection({
- start: amountAsStringForState.length,
- end: amountAsStringForState.length,
- });
+ initializeAmount(amount);
// we want to update the state only when the amount is changed
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [amount]);
@@ -218,8 +223,13 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu
return;
}
+ // Update display amount string post-edit to ensure consistency with backend amount
+ // Reference: https://github.com/Expensify/App/issues/30505
+ const backendAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount));
+ initializeAmount(backendAmount);
+
onSubmitButtonPress(currentAmount);
- }, [onSubmitButtonPress, currentAmount]);
+ }, [onSubmitButtonPress, currentAmount, initializeAmount]);
/**
* Input handler to check for a forward-delete key (or keyboard shortcut) press.
diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js
index 54a84f99fa55..ebb687b324e8 100644
--- a/src/pages/iou/steps/MoneyRequestConfirmPage.js
+++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js
@@ -22,7 +22,7 @@ import * as ReportUtils from '@libs/ReportUtils';
import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes';
import personalDetailsPropType from '@pages/personalDetailsPropType';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
@@ -61,6 +61,7 @@ const defaultProps = {
};
function MoneyRequestConfirmPage(props) {
+ const styles = useThemeStyles();
const {isOffline} = useNetwork();
const {windowWidth} = useWindowDimensions();
const prevMoneyRequestId = useRef(props.iou.id);
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js
index 7d38ee1c0282..7cb0e72356e1 100644
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js
@@ -15,7 +15,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as TransactionUtils from '@libs/TransactionUtils';
import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -52,6 +52,7 @@ const defaultProps = {
};
function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const prevMoneyRequestId = useRef(iou.id);
const optionsSelectorRef = useRef();
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
index 553ba6ae5170..925a25db9484 100755
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
@@ -17,7 +17,7 @@ import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
import personalDetailsPropType from '@pages/personalDetailsPropType';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -95,6 +95,7 @@ function MoneyRequestParticipantsSelector({
isDistanceRequest,
isSearchingForReports,
}) {
+ const styles = useThemeStyles();
const [searchTerm, setSearchTerm] = useState('');
const [newChatOptions, setNewChatOptions] = useState({
recentReports: [],
diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js
index 84082337d39b..1140bbbcf5b2 100644
--- a/src/pages/iou/steps/NewRequestAmountPage.js
+++ b/src/pages/iou/steps/NewRequestAmountPage.js
@@ -15,7 +15,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import Navigation from '@libs/Navigation/Navigation';
import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes';
import reportPropTypes from '@pages/reportPropTypes';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -55,6 +55,7 @@ const defaultProps = {
};
function NewRequestAmountPage({route, iou, report, selectedTab}) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const prevMoneyRequestID = useRef(iou.id);
diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js
index b09117719a8c..a88a07117f08 100644
--- a/src/pages/settings/AboutPage/AboutPage.js
+++ b/src/pages/settings/AboutPage/AboutPage.js
@@ -17,7 +17,7 @@ import * as Environment from '@libs/Environment/Environment';
import Navigation from '@libs/Navigation/Navigation';
import {CONTEXT_MENU_TYPES} from '@pages/home/report/ContextMenu/ContextMenuActions';
import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Link from '@userActions/Link';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
@@ -41,6 +41,7 @@ function getFlavor() {
}
function AboutPage(props) {
+ const styles = useThemeStyles();
const {translate} = props;
const popoverAnchor = useRef(null);
const waitForNavigate = useWaitForNavigation();
diff --git a/src/pages/settings/AppDownloadLinks.js b/src/pages/settings/AppDownloadLinks.js
index db86b23fa048..ab8a1f310cf4 100644
--- a/src/pages/settings/AppDownloadLinks.js
+++ b/src/pages/settings/AppDownloadLinks.js
@@ -11,7 +11,7 @@ import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import {CONTEXT_MENU_TYPES} from '@pages/home/report/ContextMenu/ContextMenuActions';
import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Link from '@userActions/Link';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
@@ -22,6 +22,7 @@ const propTypes = {
};
function AppDownloadLinksPage(props) {
+ const styles = useThemeStyles();
let popoverAnchor;
const menuItems = [
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index 207c006a31c2..36c457f8e6ed 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -34,8 +34,8 @@ import {CONTEXT_MENU_TYPES} from '@pages/home/report/ContextMenu/ContextMenuActi
import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import policyMemberPropType from '@pages/policyMemberPropType';
import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import * as Link from '@userActions/Link';
import * as PaymentMethods from '@userActions/PaymentMethods';
import * as Session from '@userActions/Session';
@@ -130,6 +130,8 @@ const defaultProps = {
};
function InitialSettingsPage(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {isExecuting, singleExecution} = useSingleExecution();
const waitForNavigate = useWaitForNavigation();
const popoverAnchor = useRef(null);
@@ -333,7 +335,7 @@ function InitialSettingsPage(props) {
{_.isEmpty(props.currentUserPersonalDetails) || _.isUndefined(props.currentUserPersonalDetails.displayName) ? (
) : (
@@ -390,7 +392,7 @@ function InitialSettingsPage(props) {
title={translate('common.settings')}
headerContent={headerContent}
headerContainerStyles={[styles.staticHeaderImage, styles.justifyContentCenter]}
- backgroundColor={themeColors.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.ROOT]}
+ backgroundColor={theme.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.ROOT]}
>
{getMenuItems}
diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js
index f715c2082f13..4dbc5fda9198 100755
--- a/src/pages/settings/Preferences/PreferencesPage.js
+++ b/src/pages/settings/Preferences/PreferencesPage.js
@@ -12,8 +12,8 @@ import Text from '@components/Text';
import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -37,6 +37,8 @@ const defaultProps = {
};
function PreferencesPage(props) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
const {isProduction} = useEnvironment();
const {translate, preferredLocale} = useLocalize();
@@ -44,7 +46,7 @@ function PreferencesPage(props) {
Navigation.goBack(ROUTES.SETTINGS)}
- backgroundColor={themeColors.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.PREFERENCES]}
+ backgroundColor={theme.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.PREFERENCES]}
illustration={LottieAnimations.PreferencesDJ}
>
diff --git a/src/pages/settings/Preferences/PriorityModePage.js b/src/pages/settings/Preferences/PriorityModePage.js
index 73c6932b6218..8f6e4564fe92 100644
--- a/src/pages/settings/Preferences/PriorityModePage.js
+++ b/src/pages/settings/Preferences/PriorityModePage.js
@@ -8,7 +8,7 @@ import SelectionList from '@components/SelectionList';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -26,6 +26,7 @@ const defaultProps = {
};
function PriorityModePage(props) {
+ const styles = useThemeStyles();
const priorityModes = _.map(_.values(CONST.PRIORITY_MODE), (mode) => ({
value: mode,
text: props.translate(`priorityModePage.priorityModes.${mode}.label`),
diff --git a/src/pages/settings/Preferences/ThemePage.js b/src/pages/settings/Preferences/ThemePage.js
index 7802faecce14..f4acd10a7230 100644
--- a/src/pages/settings/Preferences/ThemePage.js
+++ b/src/pages/settings/Preferences/ThemePage.js
@@ -9,7 +9,7 @@ import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
+import useThemeStyles from '@styles/useThemeStyles';
import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -27,6 +27,7 @@ const defaultProps = {
};
function ThemePage(props) {
+ const styles = useThemeStyles();
const localesToThemes = _.map(_.values(_.omit(CONST.THEME, 'DEFAULT')), (theme) => ({
value: theme,
text: props.translate(`themePage.themes.${theme}.label`),
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index b97bc2521e55..5f91414368a0 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -16,11 +16,11 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import withTheme, {withThemePropTypes} from '@components/withTheme';
+import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
-import styles from '@styles/styles';
-import themeColors from '@styles/themes/default';
import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -73,6 +73,8 @@ const propTypes = {
isLoadingReportData: PropTypes.bool,
...withLocalizePropTypes,
+ ...withThemeStylesPropTypes,
+ ...withThemePropTypes,
};
const defaultProps = {
@@ -268,20 +270,23 @@ class ContactMethodDetailsPage extends Component {
isVisible={this.state.isDeleteModalOpen && !isDefaultContactMethod}
danger
/>
+
{isFailedAddContactMethod && (
)}
+
{!loginData.validatedDate && !isFailedAddContactMethod && (
-
+
+
User.clearContactMethodErrors(contactMethod, 'defaultLogin')}
>