diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1ceb12a30af5..2cacdf557560 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -114,51 +114,6 @@ jobs: env: BROWSERSTACK: ${{ secrets.BROWSERSTACK }} - submitAndroid: - name: Submit Android app for production review - needs: prep - if: ${{ github.ref == 'refs/heads/production' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Ruby - uses: ruby/setup-ruby@v1.190.0 - with: - bundler-cache: true - - - name: Get Android native version - id: getAndroidVersion - run: echo "VERSION_CODE=$(grep -o 'versionCode\s\+[0-9]\+' android/app/build.gradle | awk '{ print $2 }')" >> "$GITHUB_OUTPUT" - - - name: Decrypt json w/ Google Play credentials - run: gpg --batch --yes --decrypt --passphrase="${{ secrets.LARGE_SECRET_PASSPHRASE }}" --output android-fastlane-json-key.json android-fastlane-json-key.json.gpg - working-directory: android/app - - - name: Submit Android build for review - run: bundle exec fastlane android upload_google_play_production - env: - VERSION: ${{ steps.getAndroidVersion.outputs.VERSION_CODE }} - - - name: Warn deployers if Android production deploy failed - if: ${{ failure() }} - uses: 8398a7/action-slack@v3 - with: - status: custom - custom_payload: | - { - channel: '#deployer', - attachments: [{ - color: "#DB4545", - pretext: ``, - text: `💥 Android production deploy failed. Please manually submit ${{ needs.prep.outputs.APP_VERSION }} in the . 💥`, - }] - } - env: - GITHUB_TOKEN: ${{ github.token }} - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} - android_hybrid: name: Build and deploy Android HybridApp needs: prep @@ -431,12 +386,6 @@ jobs: APPLE_DEMO_EMAIL: ${{ secrets.APPLE_DEMO_EMAIL }} APPLE_DEMO_PASSWORD: ${{ secrets.APPLE_DEMO_PASSWORD }} - - name: Submit build for App Store review - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane ios submit_for_review - env: - VERSION: ${{ steps.getIOSVersion.outputs.IOS_VERSION }} - - name: Upload iOS build to Browser Stack if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@/Users/runner/work/App/App/New Expensify.ipa" @@ -730,7 +679,7 @@ jobs: name: Post a Slack message when any platform fails to build or deploy runs-on: ubuntu-latest if: ${{ failure() }} - needs: [buildAndroid, uploadAndroid, submitAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web] + needs: [buildAndroid, uploadAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web] steps: - name: Checkout uses: actions/checkout@v4 @@ -745,21 +694,15 @@ jobs: outputs: IS_AT_LEAST_ONE_PLATFORM_DEPLOYED: ${{ steps.checkDeploymentSuccessOnAtLeastOnePlatform.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED }} IS_ALL_PLATFORMS_DEPLOYED: ${{ steps.checkDeploymentSuccessOnAllPlatforms.outputs.IS_ALL_PLATFORMS_DEPLOYED }} - needs: [buildAndroid, uploadAndroid, submitAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web] + needs: [buildAndroid, uploadAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web] if: ${{ always() }} steps: - name: Check deployment success on at least one platform id: checkDeploymentSuccessOnAtLeastOnePlatform run: | isAtLeastOnePlatformDeployed="false" - if [ ${{ github.ref }} == 'refs/heads/production' ]; then - if [ "${{ needs.submitAndroid.result }}" == "success" ]; then - isAtLeastOnePlatformDeployed="true" - fi - else - if [ "${{ needs.uploadAndroid.result }}" == "success" ]; then - isAtLeastOnePlatformDeployed="true" - fi + if [ "${{ needs.uploadAndroid.result }}" == "success" ]; then + isAtLeastOnePlatformDeployed="true" fi if [ "${{ needs.iOS.result }}" == "success" ] || \ @@ -784,14 +727,8 @@ jobs: isAllPlatformsDeployed="true" fi - if [ ${{ github.ref }} == 'refs/heads/production' ]; then - if [ "${{ needs.submitAndroid.result }}" != "success" ]; then - isAllPlatformsDeployed="false" - fi - else - if [ "${{ needs.uploadAndroid.result }}" != "success" ]; then - isAllPlatformsDeployed="false" - fi + if [ "${{ needs.uploadAndroid.result }}" != "success" ]; then + isAllPlatformsDeployed="false" fi echo "IS_ALL_PLATFORMS_DEPLOYED=$isAllPlatformsDeployed" >> "$GITHUB_OUTPUT" @@ -939,7 +876,7 @@ jobs: name: Post a Slack message when all platforms deploy successfully runs-on: ubuntu-latest if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_ALL_PLATFORMS_DEPLOYED) }} - needs: [prep, buildAndroid, uploadAndroid, submitAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] + needs: [prep, buildAndroid, uploadAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] steps: - name: 'Announces the deploy in the #announce Slack room' uses: 8398a7/action-slack@v3 @@ -993,11 +930,11 @@ jobs: postGithubComments: uses: ./.github/workflows/postDeployComments.yml if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }} - needs: [prep, buildAndroid, uploadAndroid, submitAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] + needs: [prep, buildAndroid, uploadAndroid, android_hybrid, desktop, iOS, iOS_hybrid, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] with: version: ${{ needs.prep.outputs.APP_VERSION }} env: ${{ github.ref == 'refs/heads/production' && 'production' || 'staging' }} - android: ${{ github.ref == 'refs/heads/production' && needs.submitAndroid.result || needs.uploadAndroid.result }} + android: ${{ github.ref == 'refs/heads/production' && needs.uploadAndroid.result }} android_hybrid: ${{ needs.android_hybrid.result }} ios: ${{ needs.iOS.result }} ios_hybrid: ${{ needs.iOS_hybrid.result }} diff --git a/README.md b/README.md index 6b75fbed1b2c..9f73a0012bef 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,7 @@ Often times in order to write a unit test, you may need to mock data, a componen to help run our Unit tests. * To run the **Jest unit tests**: `npm run test` +* UI tests guidelines can be found [here](tests/ui/README.md) ## Performance tests We use Reassure for monitoring performance regression. More detailed information can be found [here](tests/perf-test/README.md): diff --git a/android/app/build.gradle b/android/app/build.gradle index 907813d56e2e..672776f6ecb4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009006600 - versionName "9.0.66-0" + versionCode 1009006607 + versionName "9.0.66-7" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/android/app/src/main/java/com/expensify/chat/MainApplication.kt b/android/app/src/main/java/com/expensify/chat/MainApplication.kt index 942304c80445..ec3ac41c76c4 100644 --- a/android/app/src/main/java/com/expensify/chat/MainApplication.kt +++ b/android/app/src/main/java/com/expensify/chat/MainApplication.kt @@ -8,6 +8,7 @@ import android.database.CursorWindow import android.os.Process import androidx.multidex.MultiDexApplication import com.expensify.chat.bootsplash.BootSplashPackage +import com.expensify.chat.navbar.NavBarManagerPackage import com.expensify.chat.shortcutManagerModule.ShortcutManagerPackage import com.facebook.react.PackageList import com.facebook.react.ReactApplication @@ -36,6 +37,7 @@ class MainApplication : MultiDexApplication(), ReactApplication { add(BootSplashPackage()) add(ExpensifyAppPackage()) add(RNTextInputResetPackage()) + add(NavBarManagerPackage()) } override fun getJSMainModuleName() = ".expo/.virtual-metro-entry" diff --git a/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerModule.kt b/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerModule.kt new file mode 100644 index 000000000000..5c566df606eb --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerModule.kt @@ -0,0 +1,27 @@ +package com.expensify.chat.navbar + +import androidx.core.view.WindowInsetsControllerCompat +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.UiThreadUtil; + +class NavBarManagerModule( + private val mReactContext: ReactApplicationContext, +) : ReactContextBaseJavaModule(mReactContext) { + override fun getName(): String = "RNNavBarManager" + + @ReactMethod + fun setButtonStyle(style: String) { + UiThreadUtil.runOnUiThread { + mReactContext.currentActivity?.window?.let { + WindowInsetsControllerCompat(it, it.decorView).let { controller -> + when (style) { + "light" -> controller.isAppearanceLightNavigationBars = false + "dark" -> controller.isAppearanceLightNavigationBars = true + } + } + } + } + } +} diff --git a/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerPackage.kt b/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerPackage.kt new file mode 100644 index 000000000000..33ee64d17769 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/navbar/NavBarManagerPackage.kt @@ -0,0 +1,18 @@ +package com.expensify.chat.navbar + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class NavBarManagerPackage : ReactPackage { + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return emptyList() + } + + override fun createNativeModules(reactContext: ReactApplicationContext): List { + val modules: MutableList = ArrayList() + modules.add(NavBarManagerModule(reactContext)) + return modules + } +} diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 75126afbd407..42da35d7a493 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -7,6 +7,8 @@ diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index 0a9417820190..14b571308bb5 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -9,9 +9,10 @@ You can create as many accounts as needed in order to test your changes directly **Notes**: -1. When testing chat functionality in the app please do this between accounts you or your fellow contributors own - **do not test chatting with Concierge**, as this diverts to our customer support team. Thank you. -2. A member of our customer onboarding team gets auto-assigned to every new policy created by a non-paying account to help them set up. Please **do not interact with these teams, ask for calls, or support on your issues.** If you do need to test functionality inside the defaultRooms (#admins & #announce) for any issues you’re working on, please let them know that you are a contributor and don’t need assistance. They will proceed to ignore the chat. -3. Please **do not post in any Expensify owned public room for testing** (e.g #exfy-roadmap, #new-expensify-feedback). These rooms include real customers and investors. You can create your own public rooms, or [use this test public room](https://staging.new.expensify.com/r/2091104345528462) on either staging or production. Thanks! +1. When creating test accounts, include a `+` (plus sign) in the email address (e.g., matt+1@gmail.com). This marks the account and their associated workspaces as test accounts in Expensify, ensuring Expensify Guides are not assigned to help with account setup. +2. When testing chat functionality in the app please do this between accounts you or your fellow contributors own - **do not test chatting with Concierge**, as this diverts to our customer support team. Thank you. +3. A member of our customer onboarding team gets auto-assigned to every new policy created by a non-paying account to help them set up. Please **do not interact with these teams, ask for calls, or support on your issues.** If you do need to test functionality inside the defaultRooms (#admins & #announce) for any issues you’re working on, please let them know that you are a contributor and don’t need assistance. They will proceed to ignore the chat. +4. Please **do not post in any Expensify owned public room for testing** (e.g #exfy-roadmap, #new-expensify-feedback). These rooms include real customers and investors. You can create your own public rooms, or [use this test public room](https://staging.new.expensify.com/r/2091104345528462) on either staging or production. Thanks! #### Generating Multiple Test Accounts You can generate multiple test accounts by using a `+` postfix, for example if your email is test@test.com, you can create multiple New Expensify accounts connected to the same email address by using test+123@test.com, test+456@test.com, etc. diff --git a/docs/articles/expensify-classic/settings/Change-or-add-email-address.md b/docs/articles/expensify-classic/settings/Change-or-add-email-address.md index 754b9a7f9ac0..f6fe3d8e13b4 100644 --- a/docs/articles/expensify-classic/settings/Change-or-add-email-address.md +++ b/docs/articles/expensify-classic/settings/Change-or-add-email-address.md @@ -12,13 +12,34 @@ The primary email address on your Expensify account is the email that receives e Before you can remove a primary email address, you must add a new one to your Expensify account and make it the primary using the steps below. Email addresses must be added as a secondary login before they can be made the primary. {% include end-info.html %} +# Adding a new Secondary Login *Note: This process is currently not available from the mobile app and must be completed from the Expensify website.* 1. Hover over Settings, then click **Account**. -2. Under the Account Details tab, scroll down to the Secondary Logins section and click **Add Secondary Login**. +2. Under the Account Details > Secondary Logins > click **Add Secondary Login**. 3. Enter the email address or phone number you wish to use as a secondary login. For phone numbers, be sure to include the international code, if applicable. 4. Find the email or text message from Expensify containing the Magic Code and enter it into the field. -5. To make the new email address the primary address for your account, click **Make Primary**. + +# Changing your Primary Login +If you already have multiple email addresses linked to your account, you can change which one is listed as the Primary Login. + +1. Settings > Account > Secondary Logins. +2. Click **Make Primary** next to the email address you want to appear on your account. You can keep both logins, or you can click **Remove** next to the old email address to delete it from your account. + +# Unlinking an email from your old account +If you at one point added your personal email address as a Secondary Login to your account, and then the account was closed - for example if you had a company account and then left the company - you may want to unlink your personal email to use it with a new Expensify account. You can do this with the following steps: + +1. Navigate to the sign in page at expensify.com. +2. Enter your personal email address into the email field. +3. Click **Unlink Accounts**. +4. You will recieve a verification email to complete the unlinking of your personal address. + +# FAQ +**What does changing the primary login do?** +When you change your primary login this will update the email address that appears on your reports (old and new), in workspace account settings, and on your account. + +**Can I have multiple Seconary Logins?** +Yes, you can have an unlimited number of logins attached to your account. diff --git a/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md b/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md index 38f1e0fdd466..2ae14e822a12 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md +++ b/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md @@ -25,7 +25,7 @@ When an expense is submitted to a workspace, your approver will receive an email # How to Create an Expense -# SmartScan a receipt +## SmartScan a receipt {% include selector.html values="desktop, mobile" %} @@ -55,7 +55,7 @@ When an expense is submitted to a workspace, your approver will receive an email You can also forward receipts to receipts@expensify.com using your primary or secondary email address. SmartScan will automatically extract all the details from the receipt and add them to your expenses. {% include end-info.html %} -# Manually add an expense +## Manually add an expense {% include selector.html values="desktop, mobile" %} @@ -83,7 +83,7 @@ You can also forward receipts to receipts@expensify.com using your primary or se {% include end-selector.html %} -# Create a distance expense +## Create a distance expense {% include selector.html values="desktop, mobile" %} @@ -115,6 +115,28 @@ You can also forward receipts to receipts@expensify.com using your primary or se {% include end-selector.html %} +# How to Delete an Expense + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop or WebApp" %} +1. Click **Search > Expenses** and locate your expense. +2. Click the checkbox next to the expense(s) you wish to delete. +3. Click **# selected** in the top right corner. +4. Choose **Delete**. +5. Confirm that you wish to delete it by clicking the red **Delete** button in the popup. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap **Search**. +2. Tap and hold on the expense you wish to delete. +3. Tap **# selected**. +4. Tap **Delete**. +5. Confirm that you wish to delete it by clicking the red **Delete** button in the popup. +{% include end-option.html %} + +{% include end-selector.html %} + # Next Steps for expenses sent to an Individual - Expenses submitted to an individual are instantly sent. diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index cd38fcaaaf6c..cd2598608a0f 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -638,6 +638,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", "${PODS_ROOT}/../../node_modules/@expensify/react-native-live-markdown/parser/react-native-live-markdown-parser.js", + "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", @@ -658,6 +659,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/react-native-live-markdown-parser.js", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", @@ -842,6 +844,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", "${PODS_ROOT}/../../node_modules/@expensify/react-native-live-markdown/parser/react-native-live-markdown-parser.js", + "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", @@ -862,6 +865,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/react-native-live-markdown-parser.js", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 0feab9ddaced..45fe3eb36805 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.66.0 + 9.0.66.7 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index d1921d0b1b65..05f70824981c 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.66.0 + 9.0.66.7 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 111c5363813a..eb799cfd6323 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.66 CFBundleVersion - 9.0.66.0 + 9.0.66.7 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5ea5b19896e4..21633b432c12 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1722,7 +1722,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-keyboard-controller (1.14.1): + - react-native-keyboard-controller (1.14.4): - DoubleConversion - glog - hermes-engine @@ -2391,7 +2391,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.183): + - RNLiveMarkdown (0.1.187): - DoubleConversion - glog - hermes-engine @@ -2411,9 +2411,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/newarch (= 0.1.183) + - RNLiveMarkdown/newarch (= 0.1.187) - Yoga - - RNLiveMarkdown/newarch (0.1.183): + - RNLiveMarkdown/newarch (0.1.187): - DoubleConversion - glog - hermes-engine @@ -3236,7 +3236,7 @@ SPEC CHECKSUMS: react-native-geolocation: b9bd12beaf0ebca61a01514517ca8455bd26fa06 react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: aae312752fcdfaa2240be9a015fc41ce54087546 - react-native-keyboard-controller: 902c07f41a415b632583b384427a71770a8b02a3 + react-native-keyboard-controller: 97bb7b48fa427c7455afdc8870c2978efd9bfa3a react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: fb5112b1fa754975485884ae85a3fb6a684f49d5 react-native-pager-view: c64a744211a46202619a77509f802765d1659dba @@ -3286,7 +3286,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 8781e2529230a1bc3ea8d75e5c3cd071b6c6aed7 RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: fa9c6451960d09209bb5698745a0a66330ec53cc + RNLiveMarkdown: 8338447b39fcd86596c74b9e0e9509e365a2dd3b RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: 460d6ff97ae49c7d5708c3212c6521697c36a0c4 RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28 diff --git a/package-lock.json b/package-lock.json index 83d0c122ab2f..8749de85e87f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "new.expensify", - "version": "9.0.66-0", + "version": "9.0.66-7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.66-0", + "version": "9.0.66-7", "hasInstallScript": true, "license": "MIT", "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.183", + "@expensify/react-native-live-markdown": "0.1.187", "@expo/metro-runtime": "~3.2.3", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -103,7 +103,7 @@ "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", "react-native-plaid-link-sdk": "11.11.0", "react-native-qrcode-svg": "6.3.11", - "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-quick-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", + "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-nitro-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", "react-native-reanimated": "3.16.1", "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", @@ -3632,14 +3632,14 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.183", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.183.tgz", - "integrity": "sha512-egxknos7ghe4M5Z2rK7DvphcaxQBdxyppu5N2tdCVc/3oPO2ZtBNjDjtksqywC12wPtIYgHSgxrzvLEfbh5skw==", + "version": "0.1.187", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.187.tgz", + "integrity": "sha512-bw+dfhRN31u2xfG8LCI3e28g5EG/BfkyX1EqjPBRQlDZo4fZsdA61UFW6P8Y4rHlqspjYXJ0vk4ctECRWYl4Yg==", "license": "MIT", "workspaces": [ - "parser", - "example", - "WebExample" + "./parser", + "./example", + "./WebExample" ], "engines": { "node": ">= 18.0.0" @@ -35885,8 +35885,9 @@ }, "node_modules/react-native-quick-sqlite": { "version": "8.1.0", - "resolved": "git+ssh://git@github.com/margelo/react-native-quick-sqlite.git#99f34ebefa91698945f3ed26622e002bd79489e0", + "resolved": "git+ssh://git@github.com/margelo/react-native-nitro-sqlite.git#99f34ebefa91698945f3ed26622e002bd79489e0", "integrity": "sha512-7uuHmOEnc6SOAVoAdvkQhvaYhUZMORM75qo+v6PZoH6Qk21j5CmrcxJE3gNh0FhMfxK73hQ3ZtugC/NI2jVhrw==", + "license": "MIT", "peerDependencies": { "react": "*", "react-native": "*" diff --git a/package.json b/package.json index 4df25219e277..60f04d59605a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.66-0", + "version": "9.0.66-7", "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.", @@ -68,7 +68,7 @@ }, "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.183", + "@expensify/react-native-live-markdown": "0.1.187", "@expo/metro-runtime": "~3.2.3", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -160,7 +160,7 @@ "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", "react-native-plaid-link-sdk": "11.11.0", "react-native-qrcode-svg": "6.3.11", - "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-quick-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", + "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-nitro-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", "react-native-reanimated": "3.16.1", "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", diff --git a/patches/react-native+0.75.2+023+modal-navigation-bar-translucent.patch b/patches/react-native+0.75.2+023+modal-navigation-bar-translucent.patch new file mode 100644 index 000000000000..f8a98760b389 --- /dev/null +++ b/patches/react-native+0.75.2+023+modal-navigation-bar-translucent.patch @@ -0,0 +1,214 @@ +diff --git a/node_modules/react-native/Libraries/Modal/Modal.d.ts b/node_modules/react-native/Libraries/Modal/Modal.d.ts +index 4cc2df2..a501b27 100644 +--- a/node_modules/react-native/Libraries/Modal/Modal.d.ts ++++ b/node_modules/react-native/Libraries/Modal/Modal.d.ts +@@ -94,6 +94,11 @@ export interface ModalPropsAndroid { + * Determines whether your modal should go under the system statusbar. + */ + statusBarTranslucent?: boolean | undefined; ++ ++ /** ++ * Determines whether your modal should go under the system navigationbar. ++ */ ++ navigationBarTranslucent?: boolean | undefined; + } + + export type ModalProps = ModalBaseProps & +diff --git a/node_modules/react-native/Libraries/Modal/Modal.js b/node_modules/react-native/Libraries/Modal/Modal.js +index 1942d9e..1ffbe4c 100644 +--- a/node_modules/react-native/Libraries/Modal/Modal.js ++++ b/node_modules/react-native/Libraries/Modal/Modal.js +@@ -95,6 +95,14 @@ export type Props = $ReadOnly<{| + */ + statusBarTranslucent?: ?boolean, + ++ /** ++ * The `navigationBarTranslucent` prop determines whether your modal should go under ++ * the system navigationbar. ++ * ++ * See https://reactnative.dev/docs/modal.html#navigationbartranslucent-android ++ */ ++ navigationBarTranslucent?: ?boolean, ++ + /** + * The `hardwareAccelerated` prop controls whether to force hardware + * acceleration for the underlying window. +@@ -170,6 +178,14 @@ function confirmProps(props: Props) { + `Modal with '${props.presentationStyle}' presentation style and 'transparent' value is not supported.`, + ); + } ++ if ( ++ props.navigationBarTranslucent === true && ++ props.statusBarTranslucent !== true ++ ) { ++ console.warn( ++ 'Modal with translucent navigation bar and without translucent status bar is not supported.', ++ ); ++ } + } + } + +@@ -291,6 +307,7 @@ class Modal extends React.Component { + onDismiss={onDismiss} + visible={this.props.visible} + statusBarTranslucent={this.props.statusBarTranslucent} ++ navigationBarTranslucent={this.props.navigationBarTranslucent} + identifier={this._identifier} + style={styles.modal} + // $FlowFixMe[method-unbinding] added when improving typing for this parameters +diff --git a/node_modules/react-native/React/Views/RCTModalHostView.h b/node_modules/react-native/React/Views/RCTModalHostView.h +index 2fcdcae..0469c23 100644 +--- a/node_modules/react-native/React/Views/RCTModalHostView.h ++++ b/node_modules/react-native/React/Views/RCTModalHostView.h +@@ -27,6 +27,7 @@ + + // Android only + @property (nonatomic, assign) BOOL statusBarTranslucent; ++@property (nonatomic, assign) BOOL navigationBarTranslucent; + @property (nonatomic, assign) BOOL hardwareAccelerated; + @property (nonatomic, assign) BOOL animated; + +diff --git a/node_modules/react-native/React/Views/RCTModalHostViewManager.m b/node_modules/react-native/React/Views/RCTModalHostViewManager.m +index e2ae7e2..a694008 100644 +--- a/node_modules/react-native/React/Views/RCTModalHostViewManager.m ++++ b/node_modules/react-native/React/Views/RCTModalHostViewManager.m +@@ -118,6 +118,7 @@ - (void)invalidate + RCT_EXPORT_VIEW_PROPERTY(presentationStyle, UIModalPresentationStyle) + RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL) + RCT_EXPORT_VIEW_PROPERTY(statusBarTranslucent, BOOL) ++RCT_EXPORT_VIEW_PROPERTY(navigationBarTranslucent, BOOL) + RCT_EXPORT_VIEW_PROPERTY(hardwareAccelerated, BOOL) + RCT_EXPORT_VIEW_PROPERTY(animated, BOOL) + RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock) +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt +index d5e053c..fddda45 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt +@@ -59,6 +59,15 @@ public class ReactModalHostManager : + view.statusBarTranslucent = statusBarTranslucent + } + ++ ++ @ReactProp(name = "navigationBarTranslucent") ++ public override fun setNavigationBarTranslucent( ++ view: ReactModalHostView, ++ navigationBarTranslucent: Boolean ++ ) { ++ view.navigationBarTranslucent = navigationBarTranslucent ++ } ++ + @ReactProp(name = "hardwareAccelerated") + public override fun setHardwareAccelerated( + view: ReactModalHostView, +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +index f6e0d82..03380cb 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +@@ -46,6 +46,7 @@ import com.facebook.react.uimanager.UIManagerModule + import com.facebook.react.uimanager.events.EventDispatcher + import com.facebook.react.views.common.ContextUtils + import com.facebook.react.views.view.ReactViewGroup ++import com.facebook.react.views.view.setSystemBarsTranslucency + import java.util.Objects + import kotlin.math.abs + +@@ -78,6 +79,12 @@ public class ReactModalHostView(context: ThemedReactContext) : + createNewDialog = true + } + ++ public var navigationBarTranslucent: Boolean = false ++ set(value) { ++ field = value ++ createNewDialog = true ++ } ++ + public var animationType: String? = null + set(value) { + field = value +@@ -296,6 +303,7 @@ public class ReactModalHostView(context: ThemedReactContext) : + } else { + frameLayout.fitsSystemWindows = true + } ++ dialog?.window?.setSystemBarsTranslucency(navigationBarTranslucent) + return frameLayout + } + +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +new file mode 100644 +index 0000000..24057c4 +--- /dev/null ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +@@ -0,0 +1,53 @@ ++/* ++ * Copyright (c) Meta Platforms, Inc. and affiliates. ++ * ++ * This source code is licensed under the MIT license found in the ++ * LICENSE file in the root directory of this source tree. ++ */ ++ ++package com.facebook.react.views.view ++ ++import android.content.res.Configuration ++import android.graphics.Color ++import android.os.Build ++import android.view.Window ++import android.view.WindowManager ++import androidx.core.view.ViewCompat ++import androidx.core.view.WindowCompat ++import androidx.core.view.WindowInsetsControllerCompat ++ ++@Suppress("DEPRECATION") ++public fun Window.setSystemBarsTranslucency(isTranslucent: Boolean) { ++ WindowCompat.setDecorFitsSystemWindows(this, !isTranslucent) ++ ++ if (isTranslucent) { ++ val isDarkMode = ++ context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == ++ Configuration.UI_MODE_NIGHT_YES ++ ++ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ++ isStatusBarContrastEnforced = false ++ isNavigationBarContrastEnforced = true ++ } ++ ++ statusBarColor = Color.TRANSPARENT ++ navigationBarColor = when { ++ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Color.TRANSPARENT ++ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && !isDarkMode -> ++ Color.argb(0xe6, 0xFF, 0xFF, 0xFF) ++ else -> Color.argb(0x80, 0x1b, 0x1b, 0x1b) ++ } ++ ++ WindowInsetsControllerCompat(this, this.decorView).run { ++ isAppearanceLightNavigationBars = !isDarkMode ++ } ++ ++ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { ++ attributes.layoutInDisplayCutoutMode = when { ++ Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> ++ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS ++ else -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/node_modules/react-native/src/private/specs/components/RCTModalHostViewNativeComponent.js b/node_modules/react-native/src/private/specs/components/RCTModalHostViewNativeComponent.js +index 86bf895..58ec294 100644 +--- a/node_modules/react-native/src/private/specs/components/RCTModalHostViewNativeComponent.js ++++ b/node_modules/react-native/src/private/specs/components/RCTModalHostViewNativeComponent.js +@@ -58,6 +58,14 @@ type NativeProps = $ReadOnly<{| + */ + statusBarTranslucent?: WithDefault, + ++ /** ++ * The `navigationBarTranslucent` prop determines whether your modal should go under ++ * the system navigationbar. ++ * ++ * See https://reactnative.dev/docs/modal#navigationBarTranslucent ++ */ ++ navigationBarTranslucent?: WithDefault, ++ + /** + * The `hardwareAccelerated` prop controls whether to force hardware + * acceleration for the underlying window. diff --git a/patches/react-native-keyboard-controller+1.14.4+001+disable-android.patch b/patches/react-native-keyboard-controller+1.14.4+001+disable-android.patch deleted file mode 100644 index 8d2d81aab40a..000000000000 --- a/patches/react-native-keyboard-controller+1.14.4+001+disable-android.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -index 93c20d3..df1e846 100644 ---- a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -+++ b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -@@ -74,7 +74,7 @@ class EdgeToEdgeReactViewGroup( - } - - override fun onConfigurationChanged(newConfig: Configuration?) { -- this.reApplyWindowInsets() -+ // this.reApplyWindowInsets() - } - // endregion - -@@ -124,12 +124,12 @@ class EdgeToEdgeReactViewGroup( - } - - private fun goToEdgeToEdge(edgeToEdge: Boolean) { -- reactContext.currentActivity?.let { -- WindowCompat.setDecorFitsSystemWindows( -- it.window, -- !edgeToEdge, -- ) -- } -+ // reactContext.currentActivity?.let { -+ // WindowCompat.setDecorFitsSystemWindows( -+ // it.window, -+ // !edgeToEdge, -+ // ) -+ // } - } - - private fun setupKeyboardCallbacks() { -@@ -182,16 +182,16 @@ class EdgeToEdgeReactViewGroup( - // region State managers - private fun enable() { - this.goToEdgeToEdge(true) -- this.setupWindowInsets() -+ // this.setupWindowInsets() - this.setupKeyboardCallbacks() -- modalAttachedWatcher.enable() -+ // modalAttachedWatcher.enable() - } - - private fun disable() { - this.goToEdgeToEdge(false) -- this.setupWindowInsets() -+ // this.setupWindowInsets() - this.removeKeyboardCallbacks() -- modalAttachedWatcher.disable() -+ // modalAttachedWatcher.disable() - } - // endregion - -@@ -223,7 +223,7 @@ class EdgeToEdgeReactViewGroup( - fun forceStatusBarTranslucent(isStatusBarTranslucent: Boolean) { - if (active && this.isStatusBarTranslucent != isStatusBarTranslucent) { - this.isStatusBarTranslucent = isStatusBarTranslucent -- this.reApplyWindowInsets() -+ // this.reApplyWindowInsets() - } - } - // endregion diff --git a/patches/react-native-modal+13.0.1.patch b/patches/react-native-modal+13.0.1+001+initial.patch similarity index 100% rename from patches/react-native-modal+13.0.1.patch rename to patches/react-native-modal+13.0.1+001+initial.patch diff --git a/patches/react-native-modal+13.0.1+002+modal-navigation-bar-translucent.patch b/patches/react-native-modal+13.0.1+002+modal-navigation-bar-translucent.patch new file mode 100644 index 000000000000..a318627af02c --- /dev/null +++ b/patches/react-native-modal+13.0.1+002+modal-navigation-bar-translucent.patch @@ -0,0 +1,32 @@ +diff --git a/node_modules/react-native-modal/dist/modal.d.ts b/node_modules/react-native-modal/dist/modal.d.ts +index bd6419e..029762c 100644 +--- a/node_modules/react-native-modal/dist/modal.d.ts ++++ b/node_modules/react-native-modal/dist/modal.d.ts +@@ -46,6 +46,7 @@ declare const defaultProps: { + scrollOffsetMax: number; + scrollHorizontal: boolean; + statusBarTranslucent: boolean; ++ navigationBarTranslucent: boolean; + supportedOrientations: ("landscape" | "portrait" | "portrait-upside-down" | "landscape-left" | "landscape-right")[]; + }; + export declare type ModalProps = ViewProps & { +@@ -137,6 +138,7 @@ export declare class ReactNativeModal extends React.Component + scrollOffsetMax: number; + scrollHorizontal: boolean; + statusBarTranslucent: boolean; ++ navigationBarTranslucent: boolean; + supportedOrientations: ("landscape" | "portrait" | "portrait-upside-down" | "landscape-left" | "landscape-right")[]; + }; + state: State; +diff --git a/node_modules/react-native-modal/dist/modal.js b/node_modules/react-native-modal/dist/modal.js +index 46277ea..feec991 100644 +--- a/node_modules/react-native-modal/dist/modal.js ++++ b/node_modules/react-native-modal/dist/modal.js +@@ -38,6 +38,7 @@ const defaultProps = { + scrollOffsetMax: 0, + scrollHorizontal: false, + statusBarTranslucent: false, ++ navigationBarTranslucent: false, + supportedOrientations: ['portrait', 'landscape'], + }; + const extractAnimationFromProps = (props) => ({ diff --git a/src/App.tsx b/src/App.tsx index 643e2146e501..52904e0a06c4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,6 @@ import {PortalProvider} from '@gorhom/portal'; import React from 'react'; import {LogBox} from 'react-native'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; -import {KeyboardProvider} from 'react-native-keyboard-controller'; import {PickerStateProvider} from 'react-native-picker-select'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import '../wdyr'; @@ -15,6 +14,7 @@ import CustomStatusBarAndBackgroundContextProvider from './components/CustomStat import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import InitialURLContextProvider from './components/InitialURLContextProvider'; +import KeyboardProvider from './components/KeyboardProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; diff --git a/src/CONST.ts b/src/CONST.ts index 496b7d8b28ec..ee70e3b29668 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -212,6 +212,7 @@ const onboardingPersonalSpendMessage: OnboardingMessage = { const combinedTrackSubmitOnboardingPersonalSpendMessage: OnboardingMessage = { ...onboardingPersonalSpendMessage, tasks: [ + selfGuidedTourTask, { type: 'trackExpense', autoCompleted: false, @@ -1338,6 +1339,10 @@ const CONST = { LIGHT_CONTENT: 'light-content', DARK_CONTENT: 'dark-content', }, + NAVIGATION_BAR_BUTTONS_STYLE: { + LIGHT: 'light', + DARK: 'dark', + }, TRANSACTION: { DEFAULT_MERCHANT: 'Expense', UNKNOWN_MERCHANT: 'Unknown Merchant', @@ -2850,6 +2855,7 @@ const CONST = { ALLOW: 'personal', }, CARD_LIST_THRESHOLD: 8, + DEFAULT_EXPORT_TYPE: 'default', EXPORT_CARD_TYPES: { /** * Name of Card NVP for QBO custom export accounts @@ -6349,6 +6355,10 @@ const CONST = { PAID_ADOPTION: 'paid_adoption', }, }, + + HYBRID_APP: { + REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/components/AmountPicker/AmountSelectorModal.tsx b/src/components/AmountPicker/AmountSelectorModal.tsx index b54f6301b798..d8510aef0499 100644 --- a/src/components/AmountPicker/AmountSelectorModal.tsx +++ b/src/components/AmountPicker/AmountSelectorModal.tsx @@ -1,4 +1,5 @@ -import React, {useState} from 'react'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useRef, useState} from 'react'; import {View} from 'react-native'; import AmountForm from '@components/AmountForm'; import Button from '@components/Button'; @@ -6,6 +7,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Modal from '@components/Modal'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; @@ -16,6 +18,28 @@ function AmountSelectorModal({value, description = '', onValueSelected, isVisibl const styles = useThemeStyles(); const [currentValue, setValue] = useState(value); + const inputRef = useRef(null); + const focusTimeoutRef = useRef(null); + + const inputCallbackRef = (ref: BaseTextInputRef | null) => { + inputRef.current = ref; + }; + + useFocusEffect( + useCallback(() => { + focusTimeoutRef.current = setTimeout(() => { + if (inputRef.current && isVisible) { + inputRef.current.focus(); + } + return () => { + if (!focusTimeoutRef.current || !isVisible) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, CONST.ANIMATED_TRANSITION); + }, [isVisible, inputRef]), + ); return ( inputCallbackRef(ref)} />