diff --git a/.eslintignore b/.eslintignore index 3d966d096add..5c4b73d8a682 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,7 @@ .github/actions/**/index.js *.config.js **/.eslintrc.js +**/.eslintrc.changed.js **/node_modules/** **/dist/** android/**/build/** diff --git a/.eslintrc.changed.js b/.eslintrc.changed.js new file mode 100644 index 000000000000..c279c3e67a51 --- /dev/null +++ b/.eslintrc.changed.js @@ -0,0 +1,10 @@ +module.exports = { + plugins: ['@typescript-eslint', 'deprecation'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + }, + rules: { + 'deprecation/deprecation': 'error', + }, +}; diff --git a/.eslintrc.pr.js b/.eslintrc.pr.js deleted file mode 100644 index 63e058bf6005..000000000000 --- a/.eslintrc.pr.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - extends: './.eslintrc', - plugins: ['deprecation'], - rules: { - 'deprecation/deprecation': 'error', - }, -}; diff --git a/.github/workflows/lint-changed.yml b/.github/workflows/lint-changed.yml new file mode 100644 index 000000000000..ddb640bfe9cf --- /dev/null +++ b/.github/workflows/lint-changed.yml @@ -0,0 +1,32 @@ +name: Changed files ESLint check + +on: + workflow_call: + pull_request: + types: [opened, synchronize] + branches-ignore: [staging, production] + paths: ['**.js', '**.ts', '**.tsx', '**.json', '**.mjs', '**.cjs', 'config/.editorconfig', '.watchmanconfig', '.imgbotconfig'] + +concurrency: + group: ${{ github.ref == 'refs/heads/main' && format('{0}-{1}', github.ref, github.sha) || github.ref }}-changed-lint + cancel-in-progress: true + +jobs: + lint-changed: + name: Changed files ESLint check + if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + + - name: Run ESLint to check for deprecation warnings + run: | + # This will just fetch the latest commit from main + git fetch origin main --no-tags --depth=1 + + # shellcheck disable=SC2046 + npm run lint-changed diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4cf5c3eb287f..af6bfa17fb6e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Lint code +name: ESLint check on: workflow_call: @@ -13,7 +13,7 @@ concurrency: jobs: lint: - name: Run ESLint + name: ESLint check if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} runs-on: ubuntu-latest steps: @@ -27,21 +27,3 @@ jobs: run: npm run lint env: CI: true - - - name: Run ESLint with stricter checks on changed files - run: | - # shellcheck disable=SC2046 - npx eslint --config ./.eslintrc.pr.js $(git diff --diff-filter=AM --name-only main -- "*.js" "*.ts" "*.tsx") - - - name: Verify there's no Prettier diff - run: | - npm run prettier -- --loglevel silent - if ! git diff --name-only --exit-code; then - # shellcheck disable=SC2016 - echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' - exit 1 - fi - - - name: Run unused style searcher - shell: bash - run: ./.github/scripts/findUnusedKeys.sh diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index e5ccdfa53076..796468170275 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -13,6 +13,9 @@ jobs: lint: uses: ./.github/workflows/lint.yml + prettier: + uses: ./.github/workflows/prettier.yml + test: uses: ./.github/workflows/test.yml diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 000000000000..d5cff2d9b838 --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,37 @@ +name: Prettier check + +on: + workflow_call: + pull_request: + types: [opened, synchronize] + branches-ignore: [staging, production] + paths: ['**.js', '**.ts', '**.tsx', '**.json', '**.mjs', '**.cjs', 'config/.editorconfig', '.watchmanconfig', '.imgbotconfig'] + +concurrency: + group: ${{ github.ref == 'refs/heads/main' && format('{0}-{1}', github.ref, github.sha) || github.ref }}-prettier + cancel-in-progress: true + +jobs: + prettier: + name: Prettier check + if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + + - name: Verify there's no Prettier diff + run: | + npm run prettier -- --loglevel silent + if ! git diff --name-only --exit-code; then + # shellcheck disable=SC2016 + echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' + exit 1 + fi + + - name: Run unused style searcher + shell: bash + run: ./.github/scripts/findUnusedKeys.sh diff --git a/android/app/build.gradle b/android/app/build.gradle index b538d4595dc4..7634aea8f5f9 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 1009003500 - versionName "9.0.35-0" + versionCode 1009003601 + versionName "9.0.36-1" // 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 26a28d9955a0..2cc8b7780253 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.shortcutManagerModule.ShortcutManagerPackage import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.facebook.react.ReactNativeHost @@ -29,6 +30,7 @@ class MainApplication : MultiDexApplication(), ReactApplication { PackageList(this).packages.apply { // Packages that cannot be autolinked yet can be added manually here, for example: // add(MyReactNativePackage()); + add(ShortcutManagerPackage()) add(BootSplashPackage()) add(ExpensifyAppPackage()) add(RNTextInputResetPackage()) diff --git a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java index 8eff32dedf76..b950921a0cd5 100644 --- a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java @@ -8,6 +8,7 @@ import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.content.Context; +import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; @@ -30,10 +31,13 @@ import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.graphics.drawable.IconCompat; import androidx.versionedparcelable.ParcelUtils; import com.expensify.chat.R; +import com.expensify.chat.shortcutManagerModule.ShortcutManagerUtils; import com.urbanairship.AirshipConfigOptions; import com.urbanairship.json.JsonMap; import com.urbanairship.json.JsonValue; @@ -47,6 +51,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -205,44 +210,47 @@ private void applyMessageStyle(@NonNull Context context, NotificationCompat.Buil // Use the formatted alert message from the backend. Otherwise fallback on the message in the Onyx data. String message = alert != null ? alert : messageData.get("message").getList().get(0).getMap().get("text").getString(); - String conversationName = payload.get("roomName") == null ? "" : payload.get("roomName").getString(""); + String roomName = payload.get("roomName") == null ? "" : payload.get("roomName").getString(""); - // create the Person object who sent the latest report comment + // Create the Person object who sent the latest report comment Bitmap personIcon = fetchIcon(context, avatar); builder.setLargeIcon(personIcon); Person person = createMessagePersonObject(IconCompat.createWithBitmap(personIcon), accountID, name); + ShortcutManagerUtils.addDynamicShortcut(context, reportID, name, accountID, personIcon, person); + // Create latest received message object long createdTimeInMillis = getMessageTimeInMillis(messageData.get("created").getString("")); NotificationCompat.MessagingStyle.Message newMessage = new NotificationCompat.MessagingStyle.Message(message, createdTimeInMillis, person); - // Conversational styling should be applied to groups chats, rooms, and any 1:1 chats with more than one notification (ensuring the large profile image is always shown) - if (!conversationName.isEmpty() || hasExistingNotification) { - // Create the messaging style notification builder for this notification, associating it with the person who sent the report comment - NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(person) - .setGroupConversation(true) - .setConversationTitle(conversationName); + NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(person); + // Add all conversation messages to the notification, including the last one we just received. + List messages; + if (hasExistingNotification) { + NotificationCompat.MessagingStyle previousStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(existingReportNotification.getNotification()); + messages = previousStyle != null ? previousStyle.getMessages() : new ArrayList<>(List.of(recreatePreviousMessage(existingReportNotification))); + } else { + messages = new ArrayList<>(); + } - // Add all conversation messages to the notification, including the last one we just received. - List messages; - if (hasExistingNotification) { - NotificationCompat.MessagingStyle previousStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(existingReportNotification.getNotification()); - messages = previousStyle != null ? previousStyle.getMessages() : new ArrayList<>(List.of(recreatePreviousMessage(existingReportNotification))); - } else { - messages = new ArrayList<>(); - } - - // add the last one message we just received. - messages.add(newMessage); + // add the last one message we just received. + messages.add(newMessage); - for (NotificationCompat.MessagingStyle.Message activeMessage : messages) { - messagingStyle.addMessage(activeMessage); - } + for (NotificationCompat.MessagingStyle.Message activeMessage : messages) { + messagingStyle.addMessage(activeMessage); + } - builder.setStyle(messagingStyle); + // Conversational styling should be applied to groups chats, rooms, and any 1:1 chats with more than one notification (ensuring the large profile image is always shown) + if (!roomName.isEmpty()) { + // Create the messaging style notification builder for this notification, associating it with the person who sent the report comment + messagingStyle + .setGroupConversation(true) + .setConversationTitle(roomName); } + builder.setStyle(messagingStyle); + builder.setShortcutId(accountID); // save reportID and person info for future merging builder.addExtras(createMessageExtrasBundle(reportID, person)); diff --git a/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerModule.java b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerModule.java new file mode 100644 index 000000000000..fdb6d0ba3b97 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerModule.java @@ -0,0 +1,43 @@ +package com.expensify.chat.shortcutManagerModule; + +import static androidx.core.app.NotificationCompat.CATEGORY_MESSAGE; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.app.Person; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; + +import java.util.Collections; + +import com.expensify.chat.customairshipextender.CustomNotificationProvider; + +public class ShortcutManagerModule extends ReactContextBaseJavaModule { + private ReactApplicationContext context; + + public ShortcutManagerModule(ReactApplicationContext context) { + super(context); + this.context = context; + } + + @NonNull + @Override + public String getName() { + return "ShortcutManager"; + } + + @ReactMethod + public void removeAllDynamicShortcuts() { + ShortcutManagerUtils.removeAllDynamicShortcuts(context); + } +} diff --git a/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerPackage.java b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerPackage.java new file mode 100644 index 000000000000..d28f75592d93 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerPackage.java @@ -0,0 +1,29 @@ +package com.expensify.chat.shortcutManagerModule; + +import androidx.annotation.NonNull; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ShortcutManagerPackage implements ReactPackage { + + @NonNull + @Override + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @NonNull + @Override + public List createNativeModules(@NonNull ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new ShortcutManagerModule(reactContext)); + return modules; + } +} diff --git a/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerUtils.java b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerUtils.java new file mode 100644 index 000000000000..5947faaa67c4 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerUtils.java @@ -0,0 +1,38 @@ +package com.expensify.chat.shortcutManagerModule; + +import static androidx.core.app.NotificationCompat.CATEGORY_MESSAGE; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; + +import androidx.core.app.Person; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; + +import java.util.Collections; + +public class ShortcutManagerUtils { + public static void removeAllDynamicShortcuts(Context context) { + ShortcutManagerCompat.removeAllDynamicShortcuts(context); + } + + public static void addDynamicShortcut(Context context, long reportID, String name, String accountID, Bitmap personIcon, Person person) { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse("new-expensify://r/" + reportID)); + + ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, accountID) + .setShortLabel(name) + .setLongLabel(name) + .setCategories(Collections.singleton(CATEGORY_MESSAGE)) + .setIntent(intent) + .setLongLived(true) + .setPerson(person) + .setIcon(IconCompat.createWithBitmap(personIcon)) + .build(); + ShortcutManagerCompat.pushDynamicShortcut(context, shortcutInfo); + } + +} diff --git a/docs/articles/expensify-classic/connections/Deel.md b/docs/articles/expensify-classic/connections/Deel.md new file mode 100644 index 000000000000..12e616d9657f --- /dev/null +++ b/docs/articles/expensify-classic/connections/Deel.md @@ -0,0 +1,35 @@ +--- +title: Deel Integration +description: Automatically sync expenses from Expensify to Deel +--- + +# Overview + +This guide is for business clients who want to set up policies and synchronize expenses from Expensify to Deel. This one-way synchronization ensures that Expensify becomes the definitive source for all employee expenses. + +If you are a contractor or employee working for a company using Expensify, please refer to: + +- [Employee Guide to Using Expensify with Deel](https://help.letsdeel.com/hc/en-gb/articles/7123572847761-Employee-s-Guide-to-Using-Expensify-With-Deel) +- [Contractor Guide to Using Expensify with Deel](https://help.letsdeel.com/hc/en-gb/articles/9640208314897-How-Contractors-Can-Use-Expensify-With-Deel) + +## Introduction: + +By integrating Expensify with Deel, you can utilize Expensify’s approval workflows to ensure timely payment through Deel for your team. + +This process involves aligning user profiles and expense policies between Expensify and Deel. Once connected, Deel will scan for approved expenses from matched users included in selected workspaces for integration, allowing Deel to import these expenses for reimbursement. + +This synchronization is one-way. Expenses and receipts logged and approved in Expensify will sync to Deel. Expenses logged in Deel will not sync to Expensify. + +*Please note,* expense syncing is not immediate. For details on how syncing operates, refer to ["How Does Expense Syncing Work?"]([https://example.com](https://help.letsdeel.com/hc/en-gb/articles/5871319525521-How-To-Set-Up-The-Expensify-Integration-On-Deel-For-EOR-Employees-And-Contractors#h_01G25AWSW0KHWBA63C1AZ6X9E9)) + +## Before you begin: + +To establish a connection, make sure you have the following: + +- Deel Organization Manager permissions +- Expensify Admin permissions for policies you wish to integrate with Deel +- A paid Expensify subscription to approve expenses and sync them to Deel + +Expensify Admin permissions can be intricate. Refer to [Expensify’s Introduction to Integration]([https://example.com](https://integrations.expensify.com/Integration-Server/doc/#introduction)) for more details. + +For further steps on integration, consult the [integration guide](https://help.letsdeel.com/hc/en-gb/articles/5871319525521-How-To-Set-Up-The-Expensify-Integration-On-Deel-For-EOR-Employees-And-Contractors). diff --git a/docs/articles/expensify-classic/settings/account-settings/Add-profile-photo.md b/docs/articles/expensify-classic/settings/Add-profile-photo.md similarity index 100% rename from docs/articles/expensify-classic/settings/account-settings/Add-profile-photo.md rename to docs/articles/expensify-classic/settings/Add-profile-photo.md diff --git a/docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md b/docs/articles/expensify-classic/settings/Change-or-add-email-address.md similarity index 100% rename from docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md rename to docs/articles/expensify-classic/settings/Change-or-add-email-address.md diff --git a/docs/articles/expensify-classic/settings/account-settings/Close-or-reopen-account.md b/docs/articles/expensify-classic/settings/Close-or-reopen-account.md similarity index 100% rename from docs/articles/expensify-classic/settings/account-settings/Close-or-reopen-account.md rename to docs/articles/expensify-classic/settings/Close-or-reopen-account.md diff --git a/docs/articles/expensify-classic/settings/account-settings/Manage-devices.md b/docs/articles/expensify-classic/settings/Manage-devices.md similarity index 100% rename from docs/articles/expensify-classic/settings/account-settings/Manage-devices.md rename to docs/articles/expensify-classic/settings/Manage-devices.md diff --git a/docs/articles/expensify-classic/settings/account-settings/Merge-accounts.md b/docs/articles/expensify-classic/settings/Merge-accounts.md similarity index 100% rename from docs/articles/expensify-classic/settings/account-settings/Merge-accounts.md rename to docs/articles/expensify-classic/settings/Merge-accounts.md diff --git a/docs/articles/expensify-classic/settings/account-settings/Set-Notifications.md b/docs/articles/expensify-classic/settings/Set-Notifications.md similarity index 100% rename from docs/articles/expensify-classic/settings/account-settings/Set-Notifications.md rename to docs/articles/expensify-classic/settings/Set-Notifications.md diff --git a/docs/articles/expensify-classic/settings/account-settings/Set-time-zone.md b/docs/articles/expensify-classic/settings/Set-time-zone.md similarity index 100% rename from docs/articles/expensify-classic/settings/account-settings/Set-time-zone.md rename to docs/articles/expensify-classic/settings/Set-time-zone.md diff --git a/docs/expensify-classic/hubs/settings/account-settings.html b/docs/expensify-classic/hubs/settings/account-settings.html deleted file mode 100644 index e53f92312b7c..000000000000 --- a/docs/expensify-classic/hubs/settings/account-settings.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Account Settings ---- - -{% include section.html %} diff --git a/docs/redirects.csv b/docs/redirects.csv index 55cfd267f651..b47d6f2ae25c 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -130,7 +130,6 @@ https://help.expensify.com/articles/expensify-classic/reports/Expense-Rules,http https://help.expensify.com/articles/expensify-classic/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency https://help.expensify.com/articles/expensify-classic/reports/The-Expenses-Page,https://help.expensify.com/articles/expensify-classic/expenses/The-Expenses-Page https://help.expensify.com/articles/expensify-classic/reports/Attendee-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses -https://help.expensify.com/articles/expensify-classic/account-settings/Close-Account,https://help.expensify.com/articles/expensify-classic/settings/Close-or-reopen-account https://help.expensify.com/articles/expensify-classic/account-settings/Copilot,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/ https://help.expensify.com/articles/expensify-classic/account-settings/Notification-Troubleshooting,https://help.expensify.com/articles/expensify-classic/settings/Notification-Troubleshooting https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview @@ -199,6 +198,13 @@ https://help.expensify.com/articles/expensify-classic/workspaces/reports/Report- https://help.expensify.com/articles/expensify-classic/workspaces/reports/Scheduled-Submit,https://help.expensify.com/articles/expensify-classic/reports/Automatically-submit-employee-reports https://help.expensify.com/articles/new-expensify/chat/Expensify-Chat-For-Admins,https://help.expensify.com/new-expensify/hubs/chat/ https://help.expensify.com/articles/new-expensify/bank-accounts-and-payments/Connect-a-Bank-Account.html,https://help.expensify.com/articles/new-expensify/expenses/Connect-a-Business-Bank-Account +https://help.expensify.com/articles/expensify-classic/settings/account-settings/Add-profile-photo,https://help.expensify.com/articles/expensify-classic/settings/Add-profile-photo +https://help.expensify.com/articles/expensify-classic/settings/account-settings/Change-or-add-email-address,https://help.expensify.com/articles/expensify-classic/settings/Change-or-add-email-address +https://help.expensify.com/articles/expensify-classic/settings/account-settings/Close-or-reopen-account,https://help.expensify.com/articles/expensify-classic/settings/Close-or-reopen-account +https://help.expensify.com/articles/expensify-classic/settings/account-settings/Manage-devices,https://help.expensify.com/articles/expensify-classic/settings/Manage-devices +https://help.expensify.com/articles/expensify-classic/settings/account-settings/Merge-accounts,https://help.expensify.com/articles/expensify-classic/settings/Merge-accounts +https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-Notifications,https://help.expensify.com/articles/expensify-classic/settings/Set-Notifications +https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-time-zone,https://help.expensify.com/articles/expensify-classic/settings/Set-time-zone https://help.expensify.com/articles/expensify-classic/travel/Coming-Soon,https://help.expensify.com/expensify-classic/hubs/travel/ https://help.expensify.com/articles/new-expensify/expenses/Manually-submit-reports-for-approval,https://help.expensify.com/new-expensify/hubs/expenses/ https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursements.html,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Receive-Payments @@ -272,7 +278,6 @@ https://help.expensify.com/articles/expensify-classic/integrations/travel-integr https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Workday.html,https://help.expensify.com/articles/expensify-classic/connections/Workday https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Xero.html,https://help.expensify.com/expensify-classic/hubs/connections/xero https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/Zenefits.html,https://help.expensify.com/articles/expensify-classic/connections/Zenefits -https://help.expensify.com/articles/expensify-classic/settings/Close-or-reopen-account,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Close-or-reopen-account https://help.expensify.com/articles/new-expensify/connections/Set-Up-NetSuite-Connection,https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-to-NetSuite https://help.expensify.com/articles/new-expensify/connections/Set-Up-NetSuite-Connection.html,https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-to-NetSuite https://help.expensify.com/articles/new-expensify/connections/Set-up-QuickBooks-Online-connection,https://help.expensify.com/articles/new-expensify/connections/quickbooks-online/Connect-to-QuickBooks-Online @@ -563,9 +568,13 @@ https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Global-Reimbursements,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Enable-Global-Reimbursements https://community.expensify.com/discussion/4641/how-to-add-a-deposit-only-bank-account-both-personal-and-business,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-US-Business-Bank-Account -https://community.expensify.com/discussion/5940/how-to-get-reimbursed-outside-the-us-with-wise-for-non-us-employees,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Third-Party-Payments +https://community.expensify.com/discussion/5940/how-to-get-reimbursed-outside-the-us-with-wise-for-non-us-employees,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Get-reimbursed-faster-as-a-non-US-employee https://help.expensify.com/articles/expensify-classic/spending-insights,https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-notifications,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Set-Notifications https://help.expensify.com/articles/new-expensify/getting-started/Upgrade-to-a-Collect-Plan,https://help.expensify.com/Hidden/Upgrade-to-a-Collect-Plan https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports https://help.expensify.com/articles/new-expensify/expenses-&-payments/pay-an-invoice.html,https://help.expensify.com/articles/new-expensify/expenses-&-payments/Pay-an-invoice +https://community.expensify.com/discussion/4707/how-to-set-up-your-mobile-app,https://help.expensify.com/articles/expensify-classic/getting-started/Join-your-company's-workspace#download-the-mobile-app +https://community.expensify.com//discussion/6927/deep-dive-how-can-i-estimate-the-savings-applied-to-my-bill,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview#savings-calculator +https://community.expensify.com/discussion/5179/faq-what-does-a-policy-for-which-you-are-an-admin-has-out-of-date-billing-information-mean,https://help.expensify.com/articles/expensify-classic/expensify-billing/Out-of-date-Billing +https://community.expensify.com/discussion/6179/setting-up-a-receipt-or-travel-integration-with-expensify,https://help.expensify.com/articles/expensify-classic/connections/Additional-Travel-Integrations \ No newline at end of file diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 19e80e80c59e..768062717d4b 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 0CDA8E35287DD650004ECBEC /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0CDA8E33287DD650004ECBEC /* AppDelegate.mm */; }; 0CDA8E37287DD6A0004ECBEC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CDA8E36287DD6A0004ECBEC /* Images.xcassets */; }; 0CDA8E38287DD6A0004ECBEC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CDA8E36287DD6A0004ECBEC /* Images.xcassets */; }; + 0DFC45942C884E0A00B56C91 /* RCTShortcutManagerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DFC45932C884E0A00B56C91 /* RCTShortcutManagerModule.m */; }; + 0DFC45952C884E0A00B56C91 /* RCTShortcutManagerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DFC45932C884E0A00B56C91 /* RCTShortcutManagerModule.m */; }; 0F5BE0CE252686330097D869 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0F5BE0CD252686320097D869 /* GoogleService-Info.plist */; }; 0F5E5350263B73FD004CA14F /* EnvironmentChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */; }; 0F5E5351263B73FD004CA14F /* EnvironmentChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */; }; @@ -89,7 +91,9 @@ 083353EA2B5AB22900C603C0 /* success.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = success.mp3; path = ../assets/sounds/success.mp3; sourceTree = ""; }; 0CDA8E33287DD650004ECBEC /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = NewExpensify/AppDelegate.mm; sourceTree = ""; }; 0CDA8E36287DD6A0004ECBEC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = NewExpensify/Images.xcassets; sourceTree = ""; }; - 0D3F9E814828D91464DF9D35 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = NewExpensify/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 0D3F9E814828D91464DF9D35 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = NewExpensify/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 0DFC45922C884D7900B56C91 /* RCTShortcutManagerModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTShortcutManagerModule.h; sourceTree = ""; }; + 0DFC45932C884E0A00B56C91 /* RCTShortcutManagerModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTShortcutManagerModule.m; sourceTree = ""; }; 0F5BE0CD252686320097D869 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 0F5E534E263B73D5004CA14F /* EnvironmentChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EnvironmentChecker.h; sourceTree = ""; }; 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EnvironmentChecker.m; sourceTree = ""; }; @@ -279,6 +283,8 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + 0DFC45922C884D7900B56C91 /* RCTShortcutManagerModule.h */, + 0DFC45932C884E0A00B56C91 /* RCTShortcutManagerModule.m */, 499B0DA92BE2A1C000CABFB0 /* PrivacyInfo.xcprivacy */, 374FB8D528A133A7000D84EF /* OriginImageRequestHandler.h */, 374FB8D628A133FE000D84EF /* OriginImageRequestHandler.mm */, @@ -888,6 +894,7 @@ buildActionMask = 2147483647; files = ( 0F5E5351263B73FD004CA14F /* EnvironmentChecker.m in Sources */, + 0DFC45952C884E0A00B56C91 /* RCTShortcutManagerModule.m in Sources */, 0CDA8E35287DD650004ECBEC /* AppDelegate.mm in Sources */, 7041848626A8E47D00E09F4D /* RCTStartupTimer.m in Sources */, 7F5E81F06BCCF61AD02CEA06 /* ExpoModulesProvider.swift in Sources */, @@ -899,6 +906,7 @@ buildActionMask = 2147483647; files = ( 18D050E0262400AF000D658B /* BridgingFile.swift in Sources */, + 0DFC45942C884E0A00B56C91 /* RCTShortcutManagerModule.m in Sources */, 0F5E5350263B73FD004CA14F /* EnvironmentChecker.m in Sources */, 374FB8D728A133FE000D84EF /* OriginImageRequestHandler.mm in Sources */, 7041848526A8E47D00E09F4D /* RCTStartupTimer.m in Sources */, diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 29571b4f35b5..717ffc789054 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.35 + 9.0.36 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.35.0 + 9.0.36.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 3e9f78796e29..ee36b22be568 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.35 + 9.0.36 CFBundleSignature ???? CFBundleVersion - 9.0.35.0 + 9.0.36.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index a90aadadd876..39dbe2194b38 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.35 + 9.0.36 CFBundleVersion - 9.0.35.0 + 9.0.36.1 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6d879d275cdb..a8d151d3811d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1774,7 +1774,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-pdf (6.7.3): + - react-native-pdf (6.7.5): - DoubleConversion - glog - hermes-engine @@ -2451,7 +2451,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNReactNativeHapticFeedback (2.2.0): + - RNReactNativeHapticFeedback (2.3.1): - DoubleConversion - glog - hermes-engine @@ -3182,7 +3182,7 @@ SPEC CHECKSUMS: react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: fb5112b1fa754975485884ae85a3fb6a684f49d5 react-native-pager-view: 94195f1bf32e7f78359fa20057c97e632364a08b - react-native-pdf: dd6ae39a93607a80919bef9f3499e840c693989d + react-native-pdf: 2e2591ebd39422163850403b1c0cd7d6b351e168 react-native-performance: 3c608307be10964f8a97d3af462f37125b6d8fa5 react-native-plaid-link-sdk: f91a22b45b7c3d4cd6c47273200dc57df35068b0 react-native-quick-sqlite: 7c793c9f5834e756b336257a8d8b8239b7ceb451 @@ -3233,7 +3233,7 @@ SPEC CHECKSUMS: RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: 460d6ff97ae49c7d5708c3212c6521697c36a0c4 RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28 - RNReactNativeHapticFeedback: a15b431d2903bc2eb3474ff8d9a05d3e67a70199 + RNReactNativeHapticFeedback: 31833c3ef341d716dbbd9d64e940f0c230db46f6 RNReanimated: 76901886830e1032f16bbf820153f7dc3f02d51d RNScreens: de6e57426ba0e6cbc3fb5b4f496e7f08cb2773c2 RNShare: bd4fe9b95d1ee89a200778cc0753ebe650154bb0 @@ -3246,7 +3246,7 @@ SPEC CHECKSUMS: SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e VisionCamera: c6c8aa4b028501fc87644550fbc35a537d4da3fb - Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8 + Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae PODFILE CHECKSUM: e479ec84cb53e5fd463486d71dfee91708d3fd9a diff --git a/ios/RCTShortcutManagerModule.h b/ios/RCTShortcutManagerModule.h new file mode 100644 index 000000000000..5d596d5e7a5e --- /dev/null +++ b/ios/RCTShortcutManagerModule.h @@ -0,0 +1,4 @@ +// RCTShortcutManagerModule.h +#import +@interface RCTShortcutManagerModule : NSObject +@end diff --git a/ios/RCTShortcutManagerModule.m b/ios/RCTShortcutManagerModule.m new file mode 100644 index 000000000000..bab19019a967 --- /dev/null +++ b/ios/RCTShortcutManagerModule.m @@ -0,0 +1,11 @@ +// RCTCalendarModule.m +// iOS doesn't have dynamic shortcuts like Android, so this module contains noop functions to prevent iOS from crashing +#import "RCTShortcutManagerModule.h" + +@implementation RCTShortcutManagerModule + +RCT_EXPORT_METHOD(removeAllDynamicShortcuts){} + +RCT_EXPORT_MODULE(ShortcutManager); + +@end diff --git a/package-lock.json b/package-lock.json index 9576e6a16d72..3bda58ea9814 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.35-0", + "version": "9.0.36-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.35-0", + "version": "9.0.36-1", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -87,7 +87,7 @@ "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.18.0", "react-native-google-places-autocomplete": "2.5.6", - "react-native-haptic-feedback": "^2.2.0", + "react-native-haptic-feedback": "^2.3.1", "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#cb392140db4953a283590d7cf93b4d0461baa2a9", "react-native-key-command": "^1.0.8", @@ -95,9 +95,9 @@ "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.66", + "react-native-onyx": "2.0.68", "react-native-pager-view": "6.4.1", - "react-native-pdf": "6.7.3", + "react-native-pdf": "6.7.5", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.10.0", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", @@ -35810,8 +35810,12 @@ } }, "node_modules/react-native-haptic-feedback": { - "version": "2.2.0", - "license": "MIT", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.1.tgz", + "integrity": "sha512-dPfjV4iVHfhVyfG+nRd88ygjahbdup7KFZDM5L2aNIAzqbNtKxHZn5O1pHegwSj1t15VJliu0GyTX7XpBDeXUw==", + "workspaces": [ + "example" + ], "peerDependencies": { "react-native": ">=0.60.0" } @@ -36646,9 +36650,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.66", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.66.tgz", - "integrity": "sha512-Ns9WzcAjarAUl9g/bftf2EUJYdgcb6BAraxwqBWVeGWk3dGBR1hVEvZ7p/3rpKjidJQqiM3LWBaM6DkNHoYd1g==", + "version": "2.0.68", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.68.tgz", + "integrity": "sha512-KzcG8r6oIHRZhtiGu2XtHwYLm6eTp74r4NyhIawinfJEgcd1YMC6KdrVMqd1J7zFLTuBXPhtjiugTbUhXraFag==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -36691,8 +36695,9 @@ } }, "node_modules/react-native-pdf": { - "version": "6.7.3", - "license": "MIT", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.5.tgz", + "integrity": "sha512-d1S76p2Vwax2iG+kTnjINiUMvpjtJJvtMiYwHRbgGczT8GJjtXH49YCWOd+HfnUAU29cB+knzsKGYoZBMQM8Ow==", "dependencies": { "crypto-js": "4.2.0", "deprecated-react-native-prop-types": "^2.3.0" diff --git a/package.json b/package.json index 5092b375189c..78a63af07007 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,35 @@ { "name": "new.expensify", - "version": "9.0.35-0", + "version": "9.0.36-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", "license": "MIT", "private": true, "scripts": { - "configure-mapbox": "scripts/setup-mapbox-sdk-walkthrough.sh", - "setupNewDotWebForEmulators": "scripts/setup-newdot-web-emulators.sh", - "startAndroidEmulator": "scripts/start-android.sh", - "postinstall": "scripts/postInstall.sh", + "configure-mapbox": "./scripts/setup-mapbox-sdk-walkthrough.sh", + "setupNewDotWebForEmulators": "./scripts/setup-newdot-web-emulators.sh", + "startAndroidEmulator": "./scripts/start-android.sh", + "postinstall": "./scripts/postInstall.sh", "clean": "npx react-native clean-project-auto", - "android": "scripts/set-pusher-suffix.sh && npx react-native run-android --mode=developmentDebug --appId=com.expensify.chat.dev --active-arch-only", - "ios": "scripts/set-pusher-suffix.sh && npx react-native run-ios --list-devices --mode=\"DebugDevelopment\" --scheme=\"New Expensify Dev\"", - "pod-install": "scripts/pod-install.sh", + "android": "./scripts/set-pusher-suffix.sh && npx react-native run-android --mode=developmentDebug --appId=com.expensify.chat.dev --active-arch-only", + "ios": "./scripts/set-pusher-suffix.sh && npx react-native run-ios --list-devices --mode=\"DebugDevelopment\" --scheme=\"New Expensify Dev\"", + "pod-install": "./scripts/pod-install.sh", "ipad": "concurrently \"npx react-native run-ios --simulator=\\\"iPad Pro (12.9-inch) (6th generation)\\\" --mode=\\\"DebugDevelopment\\\" --scheme=\\\"New Expensify Dev\\\"\"", "ipad-sm": "concurrently \"npx react-native run-ios --simulator=\\\"iPad Pro (11-inch) (4th generation)\\\" --mode=\\\"DebugDevelopment\\\" --scheme=\\\"New Expensify Dev\\\"\"", "start": "npx react-native start", - "web": "scripts/set-pusher-suffix.sh && concurrently npm:web-proxy npm:web-server", + "web": "./scripts/set-pusher-suffix.sh && concurrently npm:web-proxy npm:web-server", "web-proxy": "ts-node web/proxy.ts", "web-server": "webpack-dev-server --open --config config/webpack/webpack.dev.ts", - "build": "webpack --config config/webpack/webpack.common.ts --env file=.env.production && ts-node scripts/combine-web-sourcemaps.ts", - "build-staging": "webpack --config config/webpack/webpack.common.ts --env file=.env.staging && ts-node scripts/combine-web-sourcemaps.ts", - "build-adhoc": "webpack --config config/webpack/webpack.common.ts --env file=.env.adhoc && ts-node scripts/combine-web-sourcemaps.ts", - "desktop": "scripts/set-pusher-suffix.sh && ts-node desktop/start.ts", - "desktop-build": "scripts/build-desktop.sh production", - "desktop-build-staging": "scripts/build-desktop.sh staging", + "build": "webpack --config config/webpack/webpack.common.ts --env file=.env.production && ts-node ./scripts/combine-web-sourcemaps.ts", + "build-staging": "webpack --config config/webpack/webpack.common.ts --env file=.env.staging && ts-node ./scripts/combine-web-sourcemaps.ts", + "build-adhoc": "webpack --config config/webpack/webpack.common.ts --env file=.env.adhoc && ts-node ./scripts/combine-web-sourcemaps.ts", + "desktop": "./scripts/set-pusher-suffix.sh && ts-node desktop/start.ts", + "desktop-build": "./scripts/build-desktop.sh production", + "desktop-build-staging": "./scripts/build-desktop.sh staging", "createDocsRoutes": "ts-node .github/scripts/createDocsRoutes.ts", "detectRedirectCycle": "ts-node .github/scripts/detectRedirectCycle.ts", - "desktop-build-adhoc": "scripts/build-desktop.sh adhoc", + "desktop-build-adhoc": "./scripts/build-desktop.sh adhoc", "ios-build": "fastlane ios build_unsigned", "android-build": "fastlane android build_local", "android-build-e2e": "bundle exec fastlane android build_e2e", @@ -37,7 +37,7 @@ "test": "TZ=utc NODE_OPTIONS=--experimental-vm-modules jest", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=0 --cache --cache-location=node_modules/.cache/eslint", - "lint-changed": "eslint --fix $(git diff --diff-filter=AM --name-only main -- \"*.js\" \"*.ts\" \"*.tsx\")", + "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 eslint --max-warnings=0 --config ./.eslintrc.changed.js $(git diff --diff-filter=AM --name-only origin/main HEAD -- \"*.ts\" \"*.tsx\")", "lint-watch": "npx eslint-watch --watch --changed", "shellcheck": "./scripts/shellCheck.sh", "prettier": "prettier --write .", @@ -51,11 +51,11 @@ "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.ts --env file=.env.production", "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", - "symbolicate-release:ios": "scripts/release-profile.ts --platform=ios", - "symbolicate-release:android": "scripts/release-profile.ts --platform=android", - "symbolicate-release:web": "scripts/release-profile.ts --platform=web", - "symbolicate-profile": "scripts/symbolicate-profile.ts", - "combine-web-sourcemaps": "scripts/combine-web-sourcemaps.ts", + "symbolicate-release:ios": "./scripts/release-profile.ts --platform=ios", + "symbolicate-release:android": "./scripts/release-profile.ts --platform=android", + "symbolicate-release:web": "./scripts/release-profile.ts --platform=web", + "symbolicate-profile": "./scripts/symbolicate-profile.ts", + "combine-web-sourcemaps": "./scripts/combine-web-sourcemaps.ts", "test:e2e": "ts-node tests/e2e/testRunner.ts --config ./config.local.ts", "test:e2e:dev": "ts-node tests/e2e/testRunner.ts --config ./config.dev.ts", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", @@ -144,7 +144,7 @@ "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.18.0", "react-native-google-places-autocomplete": "2.5.6", - "react-native-haptic-feedback": "^2.2.0", + "react-native-haptic-feedback": "^2.3.1", "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#cb392140db4953a283590d7cf93b4d0461baa2a9", "react-native-key-command": "^1.0.8", @@ -152,9 +152,9 @@ "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.66", + "react-native-onyx": "2.0.68", "react-native-pager-view": "6.4.1", - "react-native-pdf": "6.7.3", + "react-native-pdf": "6.7.5", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.10.0", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#da50d2c5c54e268499047f9cc98b8df4196c1ddf", diff --git a/patches/react-native-haptic-feedback+2.2.0.patch b/patches/react-native-haptic-feedback+2.2.0.patch deleted file mode 100644 index 4fb61b430869..000000000000 --- a/patches/react-native-haptic-feedback+2.2.0.patch +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/node_modules/react-native-haptic-feedback/RNReactNativeHapticFeedback.podspec b/node_modules/react-native-haptic-feedback/RNReactNativeHapticFeedback.podspec -index e692f2d..9dedd9b 100644 ---- a/node_modules/react-native-haptic-feedback/RNReactNativeHapticFeedback.podspec -+++ b/node_modules/react-native-haptic-feedback/RNReactNativeHapticFeedback.podspec -@@ -17,24 +17,7 @@ Pod::Spec.new do |s| - s.source_files = "ios/**/*.{h,m,mm}" - s.requires_arc = true - -- s.dependency 'React-Core' -- -- # This guard prevent to install the dependencies when we run `pod install` in the old architecture. -- if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then -- folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' -- -- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" -- s.pod_target_xcconfig = { -- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", -- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" -- } -- -- s.dependency "React-Codegen" -- s.dependency "RCT-Folly" -- s.dependency "RCTRequired" -- s.dependency "RCTTypeSafety" -- s.dependency "ReactCommon/turbomodule/core" -- end -+ install_modules_dependencies(s) - end - - diff --git a/patches/react-native-haptic-feedback+2.3.1.patch b/patches/react-native-haptic-feedback+2.3.1.patch new file mode 100644 index 000000000000..799bdaf7e53e --- /dev/null +++ b/patches/react-native-haptic-feedback+2.3.1.patch @@ -0,0 +1,56 @@ +diff --git a/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedback.h b/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedback.h +index c1498b9..250df1f 100644 +--- a/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedback.h ++++ b/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedback.h +@@ -1,5 +1,5 @@ + #ifdef RCT_NEW_ARCH_ENABLED +-#import "RNHapticFeedbackSpec.h" ++#import + + @interface RNHapticFeedback : NSObject + #else +diff --git a/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedbackSpec.h b/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedbackSpec.h +deleted file mode 100644 +index 6f0f81d..0000000 +--- a/node_modules/react-native-haptic-feedback/ios/RNHapticFeedback/RNHapticFeedbackSpec.h ++++ /dev/null +@@ -1,15 +0,0 @@ +-// +-// RNHapticFeedbackSpec.h +-// RNHapticFeedback +-// +-// Created by Michael Kuczera on 05.08.24. +-// Copyright © 2024 Facebook. All rights reserved. +-// +-#import +- +-@protocol NativeHapticFeedbackSpec +- +-// Indicates whether the device supports haptic feedback +-- (Boolean)supportsHaptic; +- +-@end +diff --git a/node_modules/react-native-haptic-feedback/package.json b/node_modules/react-native-haptic-feedback/package.json +index 86dfaa4..9cec8e4 100644 +--- a/node_modules/react-native-haptic-feedback/package.json ++++ b/node_modules/react-native-haptic-feedback/package.json +@@ -6,18 +6,7 @@ + "source": "src/index.ts", + "main": "./lib/commonjs/index.js", + "module": "./lib/module/index.js", +- "exports": { +- ".": { +- "import": { +- "types": "./lib/typescript/module/src/index.d.ts", +- "default": "./lib/module/index.js" +- }, +- "require": { +- "types": "./lib/typescript/commonjs/src/index.d.ts", +- "default": "./lib/commonjs/index.js" +- } +- } +- }, ++ "types": "./lib/typescript/module/src/index.d.ts", + "scripts": { + "typecheck": "tsc --noEmit --project tsconfig.test.json", + "test": "jest", diff --git a/patches/react-native-pdf+6.7.3.patch b/patches/react-native-pdf+6.7.3.patch deleted file mode 100644 index 0f0f270cefd1..000000000000 --- a/patches/react-native-pdf+6.7.3.patch +++ /dev/null @@ -1,53 +0,0 @@ -diff --git a/node_modules/react-native-pdf/react-native-pdf.podspec b/node_modules/react-native-pdf/react-native-pdf.podspec -index fb36140..5d5f19e 100644 ---- a/node_modules/react-native-pdf/react-native-pdf.podspec -+++ b/node_modules/react-native-pdf/react-native-pdf.podspec -@@ -17,24 +17,11 @@ Pod::Spec.new do |s| - s.framework = "PDFKit" - - if fabric_enabled -- folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' -- -- s.pod_target_xcconfig = { -- 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/boost" "$(PODS_ROOT)/boost-for-react-native" "$(PODS_ROOT)/RCT-Folly"', -- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", -- } - s.platforms = { ios: '11.0', tvos: '11.0' } -- s.compiler_flags = folly_compiler_flags + ' -DRCT_NEW_ARCH_ENABLED' - s.source_files = 'ios/**/*.{h,m,mm,cpp}' - s.requires_arc = true - -- s.dependency "React" -- s.dependency "React-RCTFabric" -- s.dependency "React-Codegen" -- s.dependency "RCT-Folly" -- s.dependency "RCTRequired" -- s.dependency "RCTTypeSafety" -- s.dependency "ReactCommon/turbomodule/core" -+ install_modules_dependencies(s) - else - s.platform = :ios, '8.0' - s.source_files = 'ios/**/*.{h,m,mm}' -diff --git a/node_modules/react-native-pdf/index.js b/node_modules/react-native-pdf/index.js -index c05de52..bea7af8 100644 ---- a/node_modules/react-native-pdf/index.js -+++ b/node_modules/react-native-pdf/index.js -@@ -367,11 +367,17 @@ export default class Pdf extends Component { - message[4] = message.splice(4).join('|'); - } - if (message[0] === 'loadComplete') { -+ let tableContents; -+ try { -+ tableContents = message[4]&&JSON.parse(message[4]); -+ } catch(e) { -+ tableContents = message[4]; -+ } - this.props.onLoadComplete && this.props.onLoadComplete(Number(message[1]), this.state.path, { - width: Number(message[2]), - height: Number(message[3]), - }, -- message[4]&&JSON.parse(message[4])); -+ tableContents); - } else if (message[0] === 'pageChanged') { - this.props.onPageChanged && this.props.onPageChanged(Number(message[1]), Number(message[2])); - } else if (message[0] === 'error') { diff --git a/patches/react-native-pdf+6.7.5.patch b/patches/react-native-pdf+6.7.5.patch new file mode 100644 index 000000000000..0cdcf4d5b0c9 --- /dev/null +++ b/patches/react-native-pdf+6.7.5.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/react-native-pdf/fabric/RNPDFPdfNativeComponent.js b/node_modules/react-native-pdf/fabric/RNPDFPdfNativeComponent.js +index 596d796..4e47061 100644 +--- a/node_modules/react-native-pdf/fabric/RNPDFPdfNativeComponent.js ++++ b/node_modules/react-native-pdf/fabric/RNPDFPdfNativeComponent.js +@@ -22,6 +22,7 @@ + enablePaging: ?boolean, + enableRTL: ?boolean, + enableAnnotationRendering: ?boolean, ++ enableDoubleTapZoom: ?boolean, + showsHorizontalScrollIndicator: ?boolean, + showsVerticalScrollIndicator: ?boolean, + enableAntialiasing: ?boolean, diff --git a/src/CONST.ts b/src/CONST.ts index 86cbd4c28fc9..c00b33be1c31 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -111,16 +111,16 @@ const onboardingEmployerOrSubmitMessage: OnboardingMessageType = { 'Then, send your request and wait for that sweet “Cha-ching!” when it’s complete.', }, { - type: 'enableWallet', + type: 'addBankAccount', autoCompleted: false, - title: 'Enable your wallet', + title: 'Add personal bank account', description: - 'You’ll need to *enable your Expensify Wallet* to get paid back. Don’t worry, it’s easy!\n' + + 'You’ll need to add your personal bank account to get paid back. Don’t worry, it’s easy!\n' + '\n' + - 'Here’s how to set up your wallet:\n' + + 'Here’s how to set up your bank account:\n' + '\n' + '1. Click your profile picture.\n' + - '2. Click *Wallet* > *Enable wallet*.\n' + + '2. Click *Wallet* > *Bank accounts* > *+ Add bank account*.\n' + '3. Connect your bank account.\n' + '\n' + 'Once that’s done, you can request money from anyone and get paid back right into your personal bank account.', @@ -1417,6 +1417,7 @@ const CONST = { LHN_SKELETON_VIEW_ITEM_HEIGHT: 64, SEARCH_SKELETON_VIEW_ITEM_HEIGHT: 108, EXPENSIFY_PARTNER_NAME: 'expensify.com', + EXPENSIFY_MERCHANT: 'Expensify, Inc.', EMAIL: { ACCOUNTING: 'accounting@expensify.com', ADMIN: 'admin@expensify.com', @@ -4038,70 +4039,6 @@ const CONST = { EXPENSIFY_LOGO_MARGIN_RATIO: 0.03, }, - /** - * Acceptable values for the `accessibilityRole` prop on react native components. - * - * **IMPORTANT:** Do not use with the `role` prop as it can cause errors. - * - * @deprecated ACCESSIBILITY_ROLE is deprecated. Please use CONST.ROLE instead. - */ - ACCESSIBILITY_ROLE: { - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - BUTTON: 'button', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - LINK: 'link', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - MENUITEM: 'menuitem', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - TEXT: 'text', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - RADIO: 'radio', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - IMAGEBUTTON: 'imagebutton', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - CHECKBOX: 'checkbox', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - SWITCH: 'switch', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - ADJUSTABLE: 'adjustable', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - IMAGE: 'image', - - /** - * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. - */ - TEXTBOX: 'textbox', - }, - /** * Acceptable values for the `role` attribute on react native components. * @@ -4605,6 +4542,25 @@ const CONST = { '\n' + `[Take me to workspace category settings](${workspaceCategoriesLink}).`, }, + { + type: 'setupTags', + autoCompleted: false, + title: 'Set up tags (optional)', + description: ({workspaceMoreFeaturesLink}) => + 'Tags can be used if you want more details with every expense. Use tags for projects, clients, locations, departments, and more. If you need multiple levels of tags you can upgrade to a control plan.\n' + + '\n' + + '*Here’s how to set up tags:*\n' + + '\n' + + '1. Click your profile picture.\n' + + '2. Go to Workspaces.\n' + + '3. Select your workspace.\n' + + '4. Click More features.\n' + + '5. Enable tags.\n' + + '6. Navigate to Tags in the workspace editor.\n' + + '7. In Tags, click + Add tag to make your own.\n' + + '\n' + + `*[Take me to more features](${workspaceMoreFeaturesLink})*`, + }, { type: 'addExpenseApprovals', autoCompleted: false, @@ -4716,19 +4672,19 @@ const CONST = { 'Feel free to add more details if you want, or just send it off. Let’s get you paid back!', }, { - type: 'enableWallet', + type: 'addBankAccount', autoCompleted: false, - title: 'Enable your wallet', + title: 'Add personal bank account', description: - 'You’ll need to *enable your Expensify Wallet* to get paid back. Don’t worry, it’s easy!\n' + + 'You’ll need to add your personal bank account to get paid back. Don’t worry, it’s easy!\n' + '\n' + - 'Here’s how to enable your wallet:\n' + + 'Here’s how to set up your bank account:\n' + '\n' + '1. Click your profile picture.\n' + - '2. *Click Wallet* > *Enable wallet*.\n' + - '3. Add your bank account.\n' + + '2. Click *Wallet* > *Bank accounts* > *+ Add bank account*.\n' + + '3. Connect your bank account.\n' + '\n' + - 'Once that’s done, you can request money from anyone and get paid right into your personal bank account.', + 'Once that’s done, you can request money from anyone and get paid back right into your personal bank account.', }, ], }, diff --git a/src/components/BaseMiniContextMenuItem.tsx b/src/components/BaseMiniContextMenuItem.tsx index 6e1a1e0fd229..fa0fdb45153f 100644 --- a/src/components/BaseMiniContextMenuItem.tsx +++ b/src/components/BaseMiniContextMenuItem.tsx @@ -79,7 +79,7 @@ function BaseMiniContextMenuItem( role={CONST.ROLE.BUTTON} style={({hovered, pressed}) => [ styles.reportActionContextMenuMiniButton, - StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, isDelayButtonStateComplete)), + StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, isDelayButtonStateComplete), true), isDelayButtonStateComplete && styles.cursorDefault, ]} > diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 34710a2d897e..000445abb46d 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -143,13 +143,13 @@ type ButtonProps = Partial & { type KeyboardShortcutComponentProps = Pick; -const accessibilityRoles: string[] = Object.values(CONST.ACCESSIBILITY_ROLE); +const accessibilityRoles: string[] = Object.values(CONST.ROLE); function KeyboardShortcutComponent({isDisabled = false, isLoading = false, onPress = () => {}, pressOnEnter, allowBubble, enterKeyEventListenerPriority}: KeyboardShortcutComponentProps) { const isFocused = useIsFocused(); const activeElementRole = useActiveElementRole(); - const shouldDisableEnterShortcut = useMemo(() => accessibilityRoles.includes(activeElementRole ?? '') && activeElementRole !== CONST.ACCESSIBILITY_ROLE.TEXT, [activeElementRole]); + const shouldDisableEnterShortcut = useMemo(() => accessibilityRoles.includes(activeElementRole ?? ''), [activeElementRole]); const keyboardShortcutCallback = useCallback( (event?: GestureResponderEvent | KeyboardEvent) => { diff --git a/src/components/FocusTrap/BOTTOM_TAB_SCREENS.ts b/src/components/FocusTrap/BOTTOM_TAB_SCREENS.ts index b472b80e5cbd..f6a4f5ba6e83 100644 --- a/src/components/FocusTrap/BOTTOM_TAB_SCREENS.ts +++ b/src/components/FocusTrap/BOTTOM_TAB_SCREENS.ts @@ -1,6 +1,6 @@ import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; -const BOTTOM_TAB_SCREENS = [SCREENS.HOME, SCREENS.SETTINGS.ROOT, NAVIGATORS.BOTTOM_TAB_NAVIGATOR]; +const BOTTOM_TAB_SCREENS = [SCREENS.HOME, SCREENS.SETTINGS.ROOT, NAVIGATORS.BOTTOM_TAB_NAVIGATOR, SCREENS.SEARCH.BOTTOM_TAB]; export default BOTTOM_TAB_SCREENS; diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index 97be05049e3c..d211aac7fd4c 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -82,8 +82,28 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim mixedUAStyles: {...styles.textSupporting, ...styles.textLineThrough}, contentModel: HTMLContentModel.textual, }), + blockquote: HTMLElementModel.fromCustomModel({ + tagName: 'blockquote', + contentModel: HTMLContentModel.block, + getMixedUAStyles: (tnode) => { + if (tnode.attributes.isemojisonly === undefined) { + return; + } + return styles.onlyEmojisTextLineHeight; + }, + }), }), - [styles.formError, styles.mb0, styles.colorMuted, styles.textLabelSupporting, styles.lh16, styles.textSupporting, styles.textLineThrough, styles.mutedNormalTextLabel], + [ + styles.formError, + styles.mb0, + styles.colorMuted, + styles.textLabelSupporting, + styles.lh16, + styles.textSupporting, + styles.textLineThrough, + styles.mutedNormalTextLabel, + styles.onlyEmojisTextLineHeight, + ], ); /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 8865ab103d11..18f1974535d3 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -221,7 +221,8 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti } }} withoutFocusOnSecondaryInteraction - activeOpacity={0.8} + activeOpacity={variables.pressDimValue} + opacityAnimationDuration={0} style={[ styles.flexRow, styles.alignItemsCenter, diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index f747ec7fed30..5f36e0bc0edd 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -3,7 +3,6 @@ import type {ReactElement, ReactNode} from 'react'; import React, {forwardRef, useContext, useMemo} from 'react'; import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {ActivityIndicator, View} from 'react-native'; -import type {AnimatedStyle} from 'react-native-reanimated'; import type {ValueOf} from 'type-fest'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -92,9 +91,6 @@ type MenuItemBaseProps = { /** Any additional styles to apply to the label */ labelStyle?: StyleProp; - /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle?: StyleProp>; - /** Additional styles to style the description text below the title */ descriptionTextStyle?: StyleProp; @@ -349,7 +345,6 @@ function MenuItem( containerStyle, titleStyle, labelStyle, - hoverAndPressStyle, descriptionTextStyle, badgeStyle, viewMode = CONST.OPTION_MODE.DEFAULT, @@ -575,6 +570,8 @@ function MenuItem( onPressOut={ControlSelection.unblock} onSecondaryInteraction={onSecondaryInteraction} wrapperStyle={outerWrapperStyle} + activeOpacity={variables.pressDimValue} + opacityAnimationDuration={0} style={({pressed}) => [ containerStyle, @@ -583,7 +580,6 @@ function MenuItem( !shouldRemoveBackground && StyleUtils.getButtonBackgroundColorStyle(getButtonState(focused || isHovered, pressed, success, disabled, interactive), true), ...(Array.isArray(wrapperStyle) ? wrapperStyle : [wrapperStyle]), - !focused && (isHovered || pressed) && hoverAndPressStyle, shouldGreyOutWhenDisabled && disabled && styles.buttonOpacityDisabled, isHovered && interactive && !focused && !pressed && !shouldRemoveBackground && styles.hoveredComponentBG, ] as StyleProp diff --git a/src/components/OpacityView.tsx b/src/components/OpacityView.tsx index d4a5c05167a0..f4884fd3c0f8 100644 --- a/src/components/OpacityView.tsx +++ b/src/components/OpacityView.tsx @@ -24,11 +24,24 @@ type OpacityViewProps = { */ dimmingValue?: number; + /** + * The duration of the dimming animation + * @default variables.dimAnimationDuration + */ + dimAnimationDuration?: number; + /** Whether the view needs to be rendered offscreen (for Android only) */ needsOffscreenAlphaCompositing?: boolean; }; -function OpacityView({shouldDim, children, style = [], dimmingValue = variables.hoverDimValue, needsOffscreenAlphaCompositing = false}: OpacityViewProps) { +function OpacityView({ + shouldDim, + dimAnimationDuration = variables.dimAnimationDuration, + children, + style = [], + dimmingValue = variables.hoverDimValue, + needsOffscreenAlphaCompositing = false, +}: OpacityViewProps) { const opacity = useSharedValue(1); const opacityStyle = useAnimatedStyle(() => ({ opacity: opacity.value, @@ -37,11 +50,11 @@ function OpacityView({shouldDim, children, style = [], dimmingValue = variables. React.useEffect(() => { if (shouldDim) { // eslint-disable-next-line react-compiler/react-compiler - opacity.value = withTiming(dimmingValue, {duration: 50}); + opacity.value = withTiming(dimmingValue, {duration: dimAnimationDuration}); } else { - opacity.value = withTiming(1, {duration: 50}); + opacity.value = withTiming(1, {duration: dimAnimationDuration}); } - }, [shouldDim, dimmingValue, opacity]); + }, [shouldDim, dimmingValue, opacity, dimAnimationDuration]); return ( option.isSelected); const [enteredSubMenuIndexes, setEnteredSubMenuIndexes] = useState(CONST.EMPTY_ARRAY); + const {windowHeight} = useWindowDimensions(); const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({initialFocusedIndex: currentMenuItemsFocusedIndex, maxIndex: currentMenuItems.length - 1, isActive: isVisible}); @@ -259,7 +261,7 @@ function PopoverMenu({ restoreFocusType={restoreFocusType} > - + {renderHeaderText()} {enteredSubMenuIndexes.length > 0 && renderBackButtonItem()} {currentMenuItems.map((item, menuIndex) => ( diff --git a/src/components/Pressable/PressableWithFeedback.tsx b/src/components/Pressable/PressableWithFeedback.tsx index d50404873eeb..10e6ac7bbca6 100644 --- a/src/components/Pressable/PressableWithFeedback.tsx +++ b/src/components/Pressable/PressableWithFeedback.tsx @@ -26,6 +26,12 @@ type PressableWithFeedbackProps = PressableProps & { */ hoverDimmingValue?: number; + /** + * The duration of the dimming animation + * @default variables.dimAnimationDuration + */ + dimAnimationDuration?: number; + /** Whether the view needs to be rendered offscreen (for Android only) */ needsOffscreenAlphaCompositing?: boolean; @@ -40,6 +46,7 @@ function PressableWithFeedback( needsOffscreenAlphaCompositing = false, pressDimmingValue = variables.pressDimValue, hoverDimmingValue = variables.hoverDimValue, + dimAnimationDuration, ...rest }: PressableWithFeedbackProps, ref: PressableRef, @@ -51,6 +58,7 @@ function PressableWithFeedback( diff --git a/src/components/PressableWithSecondaryInteraction/index.tsx b/src/components/PressableWithSecondaryInteraction/index.tsx index cbcf8523d9a4..810aa45ebf07 100644 --- a/src/components/PressableWithSecondaryInteraction/index.tsx +++ b/src/components/PressableWithSecondaryInteraction/index.tsx @@ -20,6 +20,7 @@ function PressableWithSecondaryInteraction( preventDefaultContextMenu = true, onSecondaryInteraction, activeOpacity = 1, + opacityAnimationDuration, ...rest }: PressableWithSecondaryInteractionProps, ref: PressableRef, @@ -100,6 +101,7 @@ function PressableWithSecondaryInteraction( wrapperStyle={[StyleUtils.combineStyles(DeviceCapabilities.canUseTouchScreen() ? [styles.userSelectNone, styles.noSelect] : [], inlineStyle), wrapperStyle]} onLongPress={onSecondaryInteraction ? executeSecondaryInteraction : undefined} pressDimmingValue={activeOpacity} + dimAnimationDuration={opacityAnimationDuration} ref={pressableRef} style={(state) => [StyleUtils.parseStyleFromFunction(style, state), inlineStyle]} needsOffscreenAlphaCompositing={needsOffscreenAlphaCompositing} diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index b07c867daeb3..ebe08cfab4f2 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -40,6 +40,12 @@ type PressableWithSecondaryInteractionProps = PressableWithFeedbackProps & { /** Opacity to reduce to when active */ activeOpacity?: number; + /** + * The duration of the opacity animation + * @default variables.dimAnimationDuration + */ + opacityAnimationDuration?: number; + /** Used to apply styles to the Pressable */ style?: ParsableStyle; diff --git a/src/components/ReportActionItem/IssueCardMessage.tsx b/src/components/ReportActionItem/IssueCardMessage.tsx index 16015311738f..d12a20c4598d 100644 --- a/src/components/ReportActionItem/IssueCardMessage.tsx +++ b/src/components/ReportActionItem/IssueCardMessage.tsx @@ -3,15 +3,15 @@ import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import RenderHTML from '@components/RenderHTML'; -import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {ReportAction} from '@src/types/onyx'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; +import type {IssueNewCardOriginalMessage} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type IssueCardMessageProps = { @@ -20,22 +20,14 @@ type IssueCardMessageProps = { policyID: string | undefined; }; -type IssueNewCardOriginalMessage = OriginalMessage< - typeof CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS | typeof CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED | typeof CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED_VIRTUAL ->; - function IssueCardMessage({action, policyID}: IssueCardMessageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const {environmentURL} = useEnvironment(); const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS); const [session] = useOnyx(ONYXKEYS.SESSION); const assigneeAccountID = (action?.originalMessage as IssueNewCardOriginalMessage)?.assigneeAccountID; - const assignee = ``; - const link = `${translate('cardPage.expensifyCard')}`; - const missingDetails = !privatePersonalDetails?.legalFirstName || !privatePersonalDetails?.legalLastName || @@ -46,25 +38,12 @@ function IssueCardMessage({action, policyID}: IssueCardMessageProps) { const isAssigneeCurrentUser = !isEmptyObject(session) && session.accountID === assigneeAccountID; - const shouldShowDetailsButton = action?.actionName === CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS && missingDetails && isAssigneeCurrentUser; - - const getTranslation = () => { - switch (action?.actionName) { - case CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED: - return translate('workspace.expensifyCard.issuedCard', assignee); - case CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED_VIRTUAL: - return translate('workspace.expensifyCard.issuedCardVirtual', {assignee, link}); - case CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS: - return translate(`workspace.expensifyCard.${!isAssigneeCurrentUser || shouldShowDetailsButton ? 'issuedCardNoShippingDetails' : 'addedShippingDetails'}`, assignee); - default: - return ''; - } - }; + const shouldShowAddMissingDetailsButton = action?.actionName === CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS && missingDetails && isAssigneeCurrentUser; return ( <> - ${getTranslation()}`} /> - {shouldShowDetailsButton && ( + ${ReportActionsUtils.getCardIssuedMessage(action, true)}`} /> + {shouldShowAddMissingDetailsButton && (