diff --git a/.eslintignore b/.eslintignore index aa10a3073f4e..26ecb1ae7cc7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -14,4 +14,3 @@ web/gtm.js src/libs/SearchParser/searchParser.js src/libs/SearchParser/autocompleteParser.js help/_scripts/** -modules/** diff --git a/Mobile-Expensify b/Mobile-Expensify index 43c5ef761b59..57cd405d6e95 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 43c5ef761b59d38a297904c5917c326d86c83fb7 +Subproject commit 57cd405d6e9523bce8377961d92af14b3ab9ac41 diff --git a/README.md b/README.md index fcc5e2b934e9..455f2f61197d 100644 --- a/README.md +++ b/README.md @@ -500,7 +500,7 @@ It's important to emphasise that a git submodule is just a **regular git reposit > #### For external contributors > > If you'd like to modify the `Mobile-Expensify` source code, it is best that you create your own fork. Then, you can swap origin of the remote repository by executing this command: -> +> > `cd Mobile-Expensify && git remote set-url origin ` > > This way, you'll attach the submodule to your fork repository. diff --git a/android/app/build.gradle b/android/app/build.gradle index cf34cd05f8fd..6069341008f7 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 1009007704 - versionName "9.0.77-4" + versionCode 1009007903 + versionName "9.0.79-3" // 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/proguard-rules.pro b/android/app/proguard-rules.pro index bef985265d7f..6d6406551cdd 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -10,7 +10,6 @@ # Add any project specific keep options here: -keep class com.expensify.chat.BuildConfig { *; } -keep class com.facebook.** { *; } --keep class com.margelo.nitro.** { *; } -keep, allowoptimization, allowobfuscation class expo.modules.** { *; } # Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items). diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a859703ae719..142d919a7a18 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,6 @@ - diff --git a/assets/images/buildings.svg b/assets/images/buildings.svg new file mode 100644 index 000000000000..42171d499f26 --- /dev/null +++ b/assets/images/buildings.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__building.svg b/assets/images/simple-illustrations/simple-illustration__building.svg new file mode 100644 index 000000000000..94a7320d8471 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__building.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/simple-illustrations/simple-illustration__buildings.svg b/assets/images/simple-illustrations/simple-illustration__buildings.svg new file mode 100644 index 000000000000..cb22c3a29ce4 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__buildings.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md index f94e692f5e56..1398e02a7a03 100644 --- a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md +++ b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md @@ -3,97 +3,156 @@ title: Troubleshooting description: How to troubleshoot company card importing in Expensify --- # Overview -Whether you're encountering issues related to company cards, require assistance with company card account access, or have questions about company card import features, you've come to the right place. +This guide helps you troubleshoot common issues with company cards in Expensify, including connection errors, missing transactions, and account setup problems. -## How to add company cards to Expensify -You can add company credit cards under the Domain settings in your Expensify account by navigating to *Settings* > *Domain* > _Domain Name_ > *Company Cards* and clicking *Import Card/Bank* and following the prompts. +## Adding company cards to Expensify +To add company credit cards: -## To Locate Missing Card Transactions in Expensify -1. **Wait for Posting**: Bank transactions may take up to 24 hours to import into Expensify after they have "posted" at your bank. Ensure sufficient time has passed for transactions to appear. -2. **Update Company Cards**: Go to Settings > Domains > Company Cards. Click on the card in question and click "Update" to refresh the card feed. -3. **Reconcile Cards**: Navigate to the Reconciliation section under Settings > Domains > Company Cards. Refer to the detailed guide on how to use the [Reconciliation Dashboard](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation#identifying-outstanding-unapproved-expenses-using-the-reconciliation-dashboard). -4. **Review Transactions**: Use the Reconciliation Dashboard to view all transactions within a specific timeframe. Transactions will display on the Expenses page based on their "Posted Date". If needed, uncheck the "use posted date" checkbox near the filters to view transactions based on their "Transaction Date" instead. -5. **Address Gaps**: If there is a significant gap in transactions or if transactions are still missing, contact Expensify's Concierge or your Account Manager. They can initiate a historical data update on your card feed to ensure all transactions are properly imported. +1. Go to **Settings** > **Domain** > _[Domain Name]_ > **Company Cards**. +2. Click **Import Card/Bank** and follow the prompts. -Following these steps should help you identify and resolve any issues with missing card transactions in Expensify. +{% include info.html %} +Only Domain Admins can connect and assign company cards in Expensify. If you're not a Domain Admin and want to connect your own credit card, follow the steps [here](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards) to connect it as a personal card. +{% include end-info.html %} -## Known issues importing transactions -The first step should always be to "Update" your card, either from Settings > Your Account > Credit Card Import or Settings > Domain > [Domain Name] > Company Cards for centrally managed cards. If a "Fix" or "Fix card" option appears, follow the steps to fix the connection. If this fails to import your missing transactions, there is a known issue whereby some transactions will not import for certain API-based company card connections. So far this has been reported on American Express, Chase and Wells Fargo. This can be temporarily resolved by creating the expenses manually instead: +## Best practices for establishing the initial card connection +To ensure a successful initial card connection in Expensify, follow these best practices: -- [Manually add the expenses](https://help.expensify.com/articles/expensify-classic/expenses/expenses/Add-an-expense) -- [Upload the expenses via CSV](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import) +- **Import in the Correct Location**: For company cards, navigate to **Settings** > **Domains** > _[Domain Name]_ > **Company Cards** > **Import Card** to establish the connection. For personal or individual card accounts, refer to the instructions [here](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards). +- **Select the Appropriate Bank Connection**: Ensure you’re selecting the appropriate bank connection for your cards. +- **Use Master or Parent Administrative Credentials**: For company cards, always use the master administrative credentials to import the entire set of cards. +- **Disable Two-Factor Authentication (2FA)**: Expensify cannot bypass bank-imposed 2FA requirements. To maintain a stable connection, temporarily disable 2FA on your bank account before attempting to connect. -# Errors connecting company cards +By following these steps, you can avoid common issues and establish a stable card connection with Expensify. + +# Resolving missing card transactions + +Here are some common steps to resolve issues with missing imported expenses: + +1. **Wait for posting.** Bank transactions may take up to 24 hours to import into Expensify after they have posted at your bank. Ensure sufficient time has passed for transactions to appear. +2. **Update company cards.** Go to **Settings** > **Domains** > _[Domain Name]_ > **Company Cards**. Click on the card in question and select **Update** to refresh the card feed. +3. **Reconcile cards.** Navigate to the **Reconciliation** section under **Settings** > **Domains** > _[Domain Name]_ > **Company Cards**. Refer to the detailed guide on how to use the [Reconciliation Dashboard](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation#identifying-outstanding-unapproved-expenses-using-the-reconciliation-dashboard). +4. **Review transactions.** Use the [Reconciliation Dashboard](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Reconciliation#identifying-outstanding-unapproved-expenses-using-the-reconciliation-dashboard) to view all transactions within a specific timeframe. Transactions will display on the **Expenses** page based on their posted date. If needed, uncheck the Use Posted Date checkbox near the filters to view transactions based on their Transaction Date instead. +5. **Address gaps.** If there is a significant gap in transactions or if transactions are still missing, contact Concierge or your Account Manager. They can initiate a historical data update on your card feed to ensure all transactions are properly imported. + +# General troubleshooting + +## Common import problems + +If company cards seem to be disconnected or not working as expected, troubleshoot by: +- Clicking **Update Card** under: + - **Settings** > **Account** > **Credit Card Import** for personal cards, or + - **Settings** > **Domains** > _[Domain Name]_ > **Company Cards** for company cards. +- If a **Fix** option appears, click on it and follow the steps to fix the connection. + +## Alternative workarounds +For persistent issues with API-based connections (e.g., American Express, Chase, Wells Fargo), the alternative option is to [manually add expenses](https://help.expensify.com/articles/expensify-classic/expenses/expenses/Add-an-expense), or [upload expenses via CSV](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import). + +## The connection is established but there are no cards to assign +When establishing the connection, you must assign cards during the same session. It isn't possible to create the connection, log out, and assign the cards later, as the connection will not stick and will require you to reattempt the connection. + +# Addressing duplicate expenses + +If a workspace member is experiencing duplicated expenses, this is typically due to: + + - A cardholder having accidentally imported the card as a personal credit card, in addition to being assigned the company card by a Domain Admin. + - To troubleshoot, have the employee navigate to **Settings** > **Account** > **Credit Card Import** and confirm that their card is only listed once. + + - The card was reassigned to the cardholder without the appropriate transaction start date being selected, resulting in a period of overlap. + - To troubleshoot, ensure expenses on the new card assignment have not been submitted. Then unassign the card and reassign it with a more appropriate start date. This action will delete all unsubmitted expenses from the new card feed. + +{% include info.html %} +Deleting a duplicate card will remove all Unapproved and Open expenses linked to that card. However, transactions associated with the remaining assigned card will remain unaffected. Any receipts attached to the deleted transactions will still appear on the Expenses page and can be reattached to the corresponding imported expense on the remaining assigned card. +{% include end-info.html %} + +# Tips for stable bank connections + +## Causes for connection breaks +Banks frequently update their APIs to enhance the security of financial information. However, for security reasons, they may not notify third-party services like Expensify in advance of these changes. Expensify's engineering team works diligently to minimize interruptions by monitoring bank connections and collaborating with banks to address updates promptly. + +## Resolving connection issues +Expensify's API-based banking connections rely on the online banking login credentials to maintain the connection. If your online banking username, password, security questions, login authentication, or card numbers change, the connection may need to be reestablished. Domain Admins can update this information in Expensify and manually reestablish the connection via **Settings** > **Domains** > _[Domain Name]_ > **Company Cards** > **Fix**. The Domain Admin will be prompted to enter the new credentials or updated information, which should reestablish the connection. + +# Common errors and resolutions + +Here are some errors that can occur when working with bank connections, and steps for resolving them: ## Error: Too many attempts -If you've been locked out while trying to import a new card, you'll need to wait a full 24 hours before trying again. This lock happens when incorrect online banking credentials are entered multiple times, and it's there for your security — it can't be removed. To avoid this, make sure your online banking credentials are correct before attempting to import your card again. - -## Error: Invalid credentials/Login failed -Verify your ability to log into your online banking portal by attempting to log into your bank account via the banking website. -Check for any potential temporary outages on your bank's end that may affect third-party connections like Expensify. -For specific card types: -- *Chase Card*: Confirm your password meets their new 8-32 character requirement. -- *Wells Fargo Card*: Ensure your password is under 14 characters. Reset it if necessary before importing your card to Expensify. If your card is already imported, update it and use the "Fix Card" option to reestablish the connection. -- *SVB Card*: Enable Direct Connect from the SVB website and use your online banking username and Direct Connect PIN instead of your password when connecting an SVB card. If connecting via *Settings* > *Domain* > _[Domain Name]_ > *Company Cards*, contact SVB for CDF feed setup. +If you've been locked out while trying to import a new card, you will need to wait a full 24 hours before trying again. This lock happens when incorrect online banking credentials are entered multiple times, and it cannot be bypassed. To avoid this, make sure your online banking credentials are correct before attempting to import your card again. + +## Error: Invalid credentials/login failed +Verify the online banking login details by accessing your bank's website directly. +- Some known bank-specific requirements are: + - **Chase**. Password must meet their 8-32 character requirement. + - **Wells Fargo**. Password must be under 14 characters. + - **SVB**. Enable Direct Connect and use the Direct Connect PIN for login. ## Error: Direct Connect not enabled -Direct Connect will need to be enabled in your account for your bank/credit card provider before you can import your card to Expensify. Please reach out to your bank to confirm if this option is available for your account, as well as get instructions on how to get this setup. +Direct Connect needs to be enabled on the bank account by your bank or credit card provider before it can be connected to Expensify. Please reach out to your bank to confirm if this option is available for your account and get instructions on how to enable it. -## Error: Account Setup -This error message typically indicates that there's something you need to do on your bank account's end. Please visit your online banking portal and check if there are any pending actions required. Once you've addressed those, you can try connecting your card again. -For Amex cardholders with multiple card programs in your Amex US Business account: To import multiple card programs into Expensify, you'll need to contact Amex and request that they separate the multiple card programs into distinct logins. For instance, you'll want to have your _Business Platinum_ cards under *"username1/password1"* and _Business Gold_ cards under *"username2/password2."* This ensures smooth integration with Expensify. +## Error: Account setup +This error message indicates that there is something you need to do on your bank account's end. Please visit your online banking portal and check if there are any pending actions required before attempting to connect your card again. -## Error: Account type not supported -If Expensify doesn't have a direct connection to your bank/credit card provider, we can still support the connection via spreadsheet import, which you can learn more about [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import). If the cards you're trying to import are company cards, it’s possible that you might be able to obtain a commercial feed directly from your bank. Please find more information on this [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds). +# Troubleshooting American Express connections -## Error: Username/Password/Questions out of date -Your company card connection is broken because we're missing some answers to some security questions. Please head to *Settings* > *Domain* > _[Domain Name]_ > *Company Cards* and click _Fix Card_. -This will require you to answer your bank's security questions. You will need to do this for each security question you have with your bank; so if you have 3 security questions, you will need to do this 3 times. +## Account roles and permissions +When connecting American Express cards to Expensify, you must use the Amex login credentials of the Primary/Basic account holder. Using other credentials, such as Supplemental Cardmember or Authorized Account Manager, will fail to load card data or may result in an error. -## Error: Account not found/Card number changed -This error message appears when you have been issued a new card, or if there's been a significant change to the account in some other way (password and/or card number change). -When your online bank/card account password has been changed, you may need to update the details on the Expensify end as well. To do this, navigate to *Settings* > *Domain* > _[Domain Name]_ > *Company Cards* and click _Fix Card_. -If there’s been a recent change to the card number, you’ll have to remove the card with the previous number and re-import the card using the new number. A Domain Admin will have to re-assign the card via *Settings* > *Domain* > _Domain Name_ > *Company Cards*. Before removing the card, please ensure *all Open reports have been submitted*, as removing the card will remove all imported transactions from the account that are associated with that card. +{% include info.html %} +In American Express, the Primary/Basic Account Holder is typically the person who applied for the American Express Business card, owns the account, manages its finances, and controls card issuance and account management. They can see all charges made by other cardmembers on their account. -## Error: General connection error -This error message states that your bank or credit card provider is under maintenance and is unavailable at this time. Try waiting a few hours before trying to import your credit card again. Check out our [status page](https://status.expensify.com/) for updates on bank/credit card connections, or you can also choose to subscribe to updates for your specific account type. +By contrast, a Supplemental Cardmember or Employee Cardmember is typically an employee on American Express accounts with access to their own card and payments. An Authorized Account Manager (AAM) has management privileges allowing them to manage the account and Supplemental Cardmembers' accounts. These roles do not have sufficient permissions in American Express to authorize the connection to Expensify, and therefore only the Primary/Basic Account Holder credentials can be used. +{% include end-info.html %} -## Error: Not seeing cards listed after a successful login -The card will only appear in the drop-down list for assignment once it’s activated and there are transactions that have been incurred and posted on the card. If not, the card won't be available to assign to the card holder until then. +## Importing multiple card programs +If you have multiple American Express card programs, contact Amex and request that they separate the multiple card programs into distinct logins. For example, you can have your _Business Platinum_ cards under *"username1/password1"* and _Business Gold_ cards under *"username2/password2"*. This ensures smooth integration with Expensify. -# Troubleshooting issues assigning company cards +## Connecting multiple company card programs under the same credentials +If you have multiple company card programs using the same credentials, you can import all programs together, which will display them under a single dropdown. Be sure to select all relevant cards each time you add cards from any program. -## Why do bank connections break? -Banks often make changes to safeguard your confidential information, and when they do, we need to update the connection between Expensify and the bank. We have a team of engineers who work closely with banks to monitor this and update our software accordingly when this happens. -The first step is to check if there have been any changes to your bank information. Have you recently changed your banking password without updating it in Expensify? Has your banking username or card number been updated? Did you update your security questions for your bank? -If you've answered "yes" to any of these questions, a Domain Admins need to update this information in Expensify and manually re-establish the connection by heading to *Settings* > *Domains* > _Domain Name_ > *Company Cards* > *Fix*. The Domain Admin will be prompted to enter the new credentials/updated information and this should reestablish the connection. +If you prefer to manage card programs separately, you can import them one at a time, ensuring you select all cards within the specific program during each import. After authorizing the account, you will be guided back to Expensify to assign the cards as needed. -## How do I resolve errors while I’m trying to import my card?* -Make sure you're importing your card in the correct spot in Expensify and selecting the right bank connection. For company cards, use the master administrative credentials to import your set of cards at *Settings* > *Domains* > _Domain Name_ > *Company Cards* > *Import Card*. -Please note there are some things that cannot be bypassed within Expensify, including two-factor authentication being enabled within your bank account. This will prevent the connection from remaining stable and will need to be turned off on the bank side. +*Important Reminder*: Whenever you need to access the connection to assign a new card, you must still choose all card programs. For example, if you have a new employee with a card under your Business Gold Rewards Card program, you will still need to authorize all the cards in that program or all the programs if you have only one dropdown menu. -## Why Can’t I See the Transactions Before a Certain Date? -When importing a card into Expensify, the platform typically retrieves 30-90 days of historical transactions, depending on the card or account type. For commercial feeds, transactions cannot be imported before the bank starts sending data. If needed, banks can send backdated files, and Expensify can run a historical update upon request. +## Adding cards under different programs with different logins +If you have multiple card programs with different credentials, you will need another Domain Admin account to add each card program from their own account. Once all Domain Admins have connected and assigned the cards they are the Primary account holder for, all cards will be listed under one *American Express (New and Upgraded)* list on the Domain Company Card page. -Additionally, Expensify does not import transactions dated before the "start date" you specify when assigning the card. Unless transitioning from an old card to a new one to avoid duplicates, it's advisable to set the start date to "earliest possible" or leave it blank. +## Amex error: Username, password, or security questions out of date +Your company card connection is broken because Expensify is missing answers to your security questions. Go to **Settings** > **Domain** > _[Domain Name]_ > **Company Cards** and click **Fix**. Answer your bank's security questions to restore the connection. Repeat this process for each security question your bank requires. -For historical expenses that cannot be imported automatically, consider using Expensify's [company card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import) or [personal card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards#importing-expenses-via-a-spreadsheet) spreadsheet import method. This allows you to manually input missing transactions into the system. +## Amex error: Account not found or card number changed +This error occurs when you have been issued a new card or if there has been a significant change to the account, such as a password or card number update. -## Why Am I / Why Is My Employee Seeing Duplicates? -If an employee is seeing duplicate expenses, they may have accidentally imported the card as a personal credit card as well as having the Domain Admin assign them a company card. +To update the connection: +1. Go to **Settings** > **Domain** > _[Domain Name]_ > **Company Cards** and click **Fix**. +2. If there has been a card number change, remove the card with the previous number and re-import the card with the new number. +3. Before removing the card, ensure all open reports have been submitted. Removing the card will delete all imported transactions associated with that card. A Domain Admin will need to re-assign the card after re-importing it. -To troubleshoot: -- Have the employee navigate to their Settings > Your Account > Credit Card Import and confirm that their card is only listed once. -- If the card is listed twice, delete the entry without the "padlock" icon. +## Amex error: General connection error +This error indicates that your bank or credit card provider is under maintenance and unavailable. Wait a few hours before trying to import your credit card again. Check Expensify's [status page](https://status.expensify.com/) for updates on bank or credit card connections, or subscribe to updates for your account type. + +## Amex error: Session has expired +If you see an error stating "Your session has expired. Please return to Expensify and try again," this means you are using incorrect Amex credentials. Use the Primary/Basic account holder credentials. If you are unsure which credentials to use, contact American Express for guidance. + +## Amex error: Card isn't eligible +This error occurs when the account is not a business account or the credentials used are not for the Primary account holder. Verify the account type and credentials before attempting to connect again. -**Important:** Deleting a duplicate card will delete all unapproved expenses from that transaction feed. Transactions associated with the remaining card will not be affected. If receipts were attached to those transactions, they will still be on the Expenses page, and the employee can click to SmartScan them again. +# Troubleshooting Chase connections -Duplicate expenses might also occur if you recently unassigned and reassigned a company card with an overlapping start date. If this is the case and expenses on the “new” copy have not been submitted, you can unassign the card again and reassign it with a more appropriate start date. This action will delete all unsubmitted expenses from the new card feed. +## Resetting Chase access to Expensify +If you are experiencing issues with your Chase connection in Expensify, resetting access can often resolve the problem. Follow these steps to troubleshoot: -## What are the most reliable bank connections in Expensify?* -All bank connections listed below are extremely reliable, but we recommend transacting with the Expensify Visa® Commercial Card. It also offers daily and monthly settlement, unapproved expense limits, realtime compliance for secure and efficient spending, and cash back on all US purchases. [Click here to learn more about the Expensify Card](https://use.expensify.com/company-credit-card). +1. Log in to your Chase account portal and visit the [Linked Apps & Websites](https://www.chase.com/digital/data-sharing) page in the Security Center. +2. Locate Expensify in the Linked Apps & Websites list. +3. Select **Stop sharing data** to disconnect Expensify's access to your Chase account. +4. After resetting access, follow the instructions [here](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting#how-to-add-company-cards-to-expensify) to reestablish the connection to Chase. -We've also teamed up with major banks worldwide to ensure a smooth import of credit card transactions into your accounts: +{% include faq-begin.md %} + +## What bank connections does Expensify offer? +Expensify offers highly reliable bank connections, but we recommend using the Expensify Visa® Commercial Card. It provides daily and monthly settlement, unapproved expense limits, real-time compliance for secure and efficient spending, and cash back on all US purchases. [Click here to learn more about the Expensify Card](https://use.expensify.com/company-credit-card). + +Alternatively, Expensify has partnered with major banks worldwide to ensure a smooth import of credit card transactions into your accounts, including: - American Express - Bank of America - Brex @@ -103,33 +162,13 @@ We've also teamed up with major banks worldwide to ensure a smooth import of cre - Stripe - Wells Fargo -Commercial feeds for company cards are the dependable connections in Expensify. If you have a corporate or commercial card account, you might have access to a daily transaction feed where expenses from Visa, Mastercard, and American Express are automatically sent to Expensify. Reach out to your banking relationship manager to check if your card program qualifies for this feature. +## What are the most stable bank connections? +Commercial feeds for company cards are the most dependable connections in Expensify and are considered more stable than API-based connections. If you have a corporate or commercial card account, you might have access to a daily transaction feed where expenses from Visa, Mastercard, and American Express are automatically sent to Expensify. Contact your banking relationship manager to check if your card program qualifies for this feature. -# Troubleshooting American Express Business +## Why can’t I see the transactions before a certain date? +When importing a card into Expensify, the bank typically provides 30-90 days of historical transactions, depending on the card or account type. For commercial feeds, transactions cannot be imported before the bank starts sending data, however banks can send backdated files if historical transactions are needed. -## Amex account roles -American Express provides three different roles for accessing accounts on their website. When connecting Amex cards to Expensify, it's crucial to use the credentials of the Primary/Basic account holder. Here's what each role means: -- *Primary/Basic Account Holder*: The person who applied for the American Express Business card, owns the account, manages its finances, and controls card issuance and account management. They can view all charges by other cardmembers on their account. They can see all charges made by other cardmembers on their account. -- *Supplemental Cardmember (Employee Cardmember)*: Chosen by the Primary Card Member (typically an employee on business accounts), they can access their own card info and make payments but can't see other account details. -- *Authorized Account Manager (AAM)*: Chosen by the Primary Card Member, AAMs can manage the account online or by phone, but they can't link cards to services like Expensify. They have admin rights, including adding cards, making payments, canceling cards, and setting limits. To connect cards to Expensify, use the Primary Card Holder's credentials for full access. - -## The connection is established but there are no cards to assign +Additionally, Expensify does not import transactions dated before the "start date" you specify when assigning the card. Unless transitioning from an old card to a new one to avoid duplicates, it is advisable to set the start date to "earliest possible" or leave it blank. For historical expenses that cannot be imported automatically, consider using Expensify's [company card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/CSV-Import) or [personal card](https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards#importing-expenses-via-a-spreadsheet) spreadsheet import method to manually input missing transactions into the system. -When establishing the connection, you must assign cards during the same session. It isn't possible to create the connection, log out, and assign the cards later, as the connection will not stick, and require you to reattempt the connection again. +{% include faq-end.md %} -## Amex error: Card isn't eligible -This error comes directly from American Express and is typically related to an account that is not a business account or using credentials that are not the primary account holder credentials. - -## Amex error: Session has expired -If you get an error stating an American Express Business Card “Your session has expired. Please return to Expensify and try again, this always means that you are using the incorrect credentials. Remember, you need to use primary/basic cardholder credentials. If you are not sure which credentials you should use, reach out to American Express for guidance. - -## Connect multiple company card programs under the same credentials -If you have multiple company card programs with the same credentials, you can select ALL programs at once. With this, all programs will be under one dropdown. Make sure to select all cards each time you are adding any cards from any program. -If you would like your card programs listed under separate dropdowns, you can select only that group making sure to select all cards from that group each time you are adding a new card. -Once you have authorized the account, you’ll be guided back to Expensify where you’ll assign all necessary cards across all programs. -This will store all cards under the same American Express Business connection dropdown and allow all cards to be added to Expensify for you to assign to users. -*Important Reminder*: Whenever you need to access the connection to assign a new card, you must still choose "ALL card programs." For instance, if you have a new employee with a card under your Business Gold Rewards Card program, you'll still need to authorize all the cards in that program or all the programs if you have only one dropdown menu! - -## Add cards under different programs with different logins -If you have multiple card programs with different credentials, you will need to have another Domain Admin account add each card program from their own account. -Once all Domain Admins have connected and assigned the cards that they are the Primary account holder for, all cards will be listed under one *American Express (New and Upgraded)* list in the Domain Company Card page. diff --git a/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md index 68bca5228913..ec3d45b3ac08 100644 --- a/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md +++ b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md @@ -39,14 +39,14 @@ The three options for the date your report will export with are: ## Accounting Method This dictates when reimbursable expenses will export, according to your preferred accounting method: -- Accrual: Out of pocket expenses will export immediately when the report is final approved -- Cash: Out of pocket expenses will export when paid via Expensify or marked as Reimbursed +- Accrual: Out-of-pocket expenses will export immediately when the report is final approved +- Cash: Out-of-pocket expenses will export when paid via Expensify or marked as Reimbursed ## Export Settings for Reimbursable Expenses **Expense Reports:** Expensify transactions will export reimbursable expenses as expense reports by default, which will be posted to the payables account designated in NetSuite. -**Vendor Bills:** Expensify transactions export as vendor bills in NetSuite and will be mapped to the subsidiary associated with the corresponding workspace. Each report will be posted as payable to the vendor associated with the employee who submitted the report. You can also set an approval level in NetSuite for vendor bills. +**Vendor Bills:** Expensify transactions export as vendor bills in NetSuite and are mapped to the subsidiary associated with the corresponding workspace. Each report is posted as payable to the vendor associated with the employee who submitted it. You can also set an approval level in NetSuite for vendor bills. **Journal Entries:** Expensify transactions that are set to export as journal entries in NetSuite will be mapped to the subsidiary associated with this workspace. All the transactions will be posted to the payable account specified in the workspace. You can also set an approval level in NetSuite for the journal entries. @@ -63,7 +63,7 @@ This dictates when reimbursable expenses will export, according to your preferre - Journal entry forms do not contain a customer column, so it is not possible to export customers or projects with this export option - The credit line and header level classifications are pulled from the employee record -**Expense Reports:** To use the expense report option for your corporate card expenses, you will need to set up your default corporate cards in NetSuite. +**Expense Reports:** To use the expense report option for your corporate card expenses, you must set up your default corporate cards in NetSuite. To use a default corporate card for non-reimbursable expenses, you must select the correct card on the employee records (for individual accounts) or the subsidiary record (If you use a non-one world account, the default is found in your accounting preferences). @@ -87,6 +87,8 @@ When selecting the option to export non-reimbursable expenses as vendor bills, t The Coding tab is where NetSuite information is configured in Expensify, which allows employees to code expenses and reports accurately. There are several coding options in NetSuite. Let’s go over each of those below. +![Insert alt text for accessibility here]({{site.url}}/assets/images/NetSuite_Configure_08.png){:width="100%"} + ## Expense Categories Expensify's integration with NetSuite automatically imports NetSuite Expense Categories as Categories in Expensify. @@ -225,6 +227,8 @@ From there, you should see the values for the Custom Lists under the Tag or Repo The NetSuite integration’s advanced configuration settings are accessed under **Settings > Workspaces > Group > _[Workspace Name]_ > Connections > NetSuite > Configure > Advanced tab**. +![Insert alt text for accessibility here]({{site.url}}/assets/images/NetSuite_Configure_09.png){:width="100%"} + Let’s review the different advanced settings and how they interact with the integration. ## Auto Sync diff --git a/docs/articles/expensify-classic/domains/SAML-SSO.md b/docs/articles/expensify-classic/domains/SAML-SSO.md index da4bd5639120..df73cf5d54c0 100644 --- a/docs/articles/expensify-classic/domains/SAML-SSO.md +++ b/docs/articles/expensify-classic/domains/SAML-SSO.md @@ -17,7 +17,7 @@ Once the domain is verified, you can access the SSO settings by navigating to Se **Below are instructions for setting up Expensify for specific SSO providers:** - [Amazon Web Services (AWS SSO)](https://static.global.sso.amazonaws.com/app-202a715cb67cddd9/instructions/index.htm) - [Google SAML](https://support.google.com/a/answer/7371682) (for GSuite, not Google SSO) -- [Microsoft Azure Active Directory](https://azure.microsoft.com/en-us/documentation/articles/active-directory-saas-expensify-tutorial/) +- [Microsoft Entra ID (formerly Azure Active Directory)](https://learn.microsoft.com/en-us/entra/identity/saas-apps/expensify-tutorial) - [Okta](https://saml-doc.okta.com/SAML_Docs/How-to-Configure-SAML-2.0-for-Expensify.html) - [OneLogin](https://onelogin.service-now.com/support?id=kb_article&sys_id=e44c9e52db187410fe39dde7489619ba) - [Oracle Identity Cloud Service](https://docs.oracle.com/en/cloud/paas/identity-cloud/idcsc/expensify.html#Expensify) @@ -39,13 +39,13 @@ The entityID for Expensify is https://expensify.com. Remember not to copy and pa ## Can you have multiple domains with only one entity ID? Yes. Please send a message to the Concierge or your account manager, and we will enable the use of the same entity ID with multiple domains. -## How can I update the Microsoft Azure SSO Certificate? +## How can I update the Microsoft Entra ID SSO Certificate? Expensify's SAML configuration doesn't support multiple active certificates. This means that if you create the new certification ahead of time without first removing the old one, the respective IDP will include two unique x509 certificates instead of one, and the connection will break. Should you need to access Expensify, switching back to the old certificate will continue to allow access while that certificate is still valid. -**To transfer from one Microsoft Azure certificate to another, please follow the below steps:** -1. In Azure Directory, create your new certificate. -2. In Azure Director, remove the old, expiring certificate. -3. In Azure Directory, activate the remaining certificate and get a new IDP for Expensify from it. +**To transfer from one Microsoft Entra certificate to another, please follow the below steps:** +1. In Microsoft Entra, create your new certificate. +2. In Microsoft Entra, remove the old, expiring certificate. +3. In Microsoft Entra, activate the remaining certificate and get a new IDP for Expensify from it. 4. In Expensify, replace the previous IDP with the new IDP. 5. Log in via SSO. If login continues to fail, write to Concierge for assistance. 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 1b1702c6fcc7..2157e05aa377 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md +++ b/docs/articles/new-expensify/expenses-&-payments/Create-an-expense.md @@ -51,10 +51,10 @@ When an expense is submitted to a workspace, your approver will receive an email {% include end-selector.html %} -![Click Global Create]({{site.url}}/assets/images/ExpensifyHelp-CreateExpense-1.png){:width="100%"} -![Click Submit expense]({{site.url}}/assets/images/ExpensifyHelp-CreateExpense-2.png){:width="100%"} -![Click Scan]({{site.url}}/assets/images/ExpensifyHelp-CreateExpense-3.png){:width="100%"} -![Enter workspace or individual's name]({{site.url}}/assets/images/ExpensifyHelp-CreateExpense-4.png){:width="100%"} +![Click Global Create]({{site.url}}/assets/images/ExpensifyHelp-CreateExpenseUpdate-1.png){:width="100%"} +![Click Create Expense]({{site.url}}/assets/images/ExpensifyHelp-CreateExpenseUpdate-2.png){:width="100%"} +![Click Scan]({{site.url}}/assets/images/ExpensifyHelp-CreateExpenseUpdate-3.png){:width="100%"} +![Enter workspace or individual's name]({{site.url}}/assets/images/ExpensifyHelp-CreateExpenseUpdate-4.png){:width="100%"} {% include info.html %} 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. diff --git a/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-1.png b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-1.png new file mode 100644 index 000000000000..18318f782466 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-1.png differ diff --git a/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-2.png b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-2.png new file mode 100644 index 000000000000..641c32a6a6b6 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-2.png differ diff --git a/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-3.png b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-3.png new file mode 100644 index 000000000000..48c6f12fb75c Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-3.png differ diff --git a/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-4.png b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-4.png new file mode 100644 index 000000000000..5f8af1e46ac4 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-CreateExpenseUpdate-4.png differ diff --git a/docs/assets/images/OldDot - Create & Pay Bills 1.png b/docs/assets/images/OldDot - Create & Pay Bills 1.png new file mode 100644 index 000000000000..a880e012408a Binary files /dev/null and b/docs/assets/images/OldDot - Create & Pay Bills 1.png differ diff --git a/docs/assets/images/OldDot - Create & Pay Bills 2.png b/docs/assets/images/OldDot - Create & Pay Bills 2.png new file mode 100644 index 000000000000..ce022a95c6a1 Binary files /dev/null and b/docs/assets/images/OldDot - Create & Pay Bills 2.png differ diff --git a/docs/assets/images/OldDot - Create & Pay Bills 3.png b/docs/assets/images/OldDot - Create & Pay Bills 3.png new file mode 100644 index 000000000000..071bcc997934 Binary files /dev/null and b/docs/assets/images/OldDot - Create & Pay Bills 3.png differ diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 108706d79a0c..2c2f051232d2 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.77 + 9.0.79 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.77.4 + 9.0.79.3 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index ea782231aaec..c253b159843a 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.77 + 9.0.79 CFBundleSignature ???? CFBundleVersion - 9.0.77.4 + 9.0.79.3 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index b14e33cdde82..e1b64682a244 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.77 + 9.0.79 CFBundleVersion - 9.0.77.4 + 9.0.79.3 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile b/ios/Podfile index bdad8a0ec396..41dc5179752d 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -24,7 +24,6 @@ prepare_react_native_project! setup_permissions([ 'Camera', - 'Contacts', 'LocationAccuracy', 'LocationAlways', 'LocationWhenInUse' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0389642465da..e9532fc1ae30 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -28,28 +28,6 @@ PODS: - AppAuth/Core - AppLogs (0.1.0) - boost (1.84.0) - - ContactsModule (0.0.1): - - DoubleConversion - - glog - - hermes-engine - - NitroModules - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - DoubleConversion (1.1.6) - EXAV (14.0.7): - ExpoModulesCore @@ -309,29 +287,6 @@ PODS: - nanopb/encode (= 2.30908.0) - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - - NitroModules (0.18.1): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-callinvoker - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - Onfido (29.7.2) - onfido-react-native-sdk (10.6.0): - DoubleConversion @@ -2795,7 +2750,6 @@ DEPENDENCIES: - AirshipServiceExtension - AppLogs (from `../node_modules/react-native-app-logs/AppLogsPod`) - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - ContactsModule (from `../modules/ContactsNitroModule`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXAV (from `../node_modules/expo-av/ios`) - EXImageLoader (from `../node_modules/expo-image-loader/ios`) @@ -2811,7 +2765,6 @@ DEPENDENCIES: - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - lottie-react-native (from `../node_modules/lottie-react-native`) - - NitroModules (from `../node_modules/react-native-nitro-modules`) - "onfido-react-native-sdk (from `../node_modules/@onfido/react-native-sdk`)" - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) @@ -2962,8 +2915,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-app-logs/AppLogsPod" boost: :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" - ContactsModule: - :path: "../modules/ContactsNitroModule" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXAV: @@ -2995,8 +2946,6 @@ EXTERNAL SOURCES: :tag: hermes-2024-08-15-RNv0.75.1-4b3bf912cc0f705b51b71ce1a5b8bd79b93a451b lottie-react-native: :path: "../node_modules/lottie-react-native" - NitroModules: - :path: "../node_modules/react-native-nitro-modules" onfido-react-native-sdk: :path: "../node_modules/@onfido/react-native-sdk" RCT-Folly: @@ -3211,7 +3160,6 @@ SPEC CHECKSUMS: AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa AppLogs: 3bc4e9b141dbf265b9464409caaa40416a9ee0e0 boost: 26992d1adf73c1c7676360643e687aee6dda994b - ContactsModule: 21671b28654413dc28795d1afc3b12eaffa28ed1 DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 EXAV: afa491e598334bbbb92a92a2f4dd33d7149ad37f EXImageLoader: ab589d67d6c5f2c33572afea9917304418566334 @@ -3251,7 +3199,6 @@ SPEC CHECKSUMS: MapboxMaps: e76b14f52c54c40b76ddecd04f40448e6f35a864 MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 - NitroModules: ebe2ba2d01dc03c1f82441561fe6062b8c3c4366 Onfido: f3af62ea1c9a419589c133e3e511e5d2c4f3f8af onfido-react-native-sdk: 4ccfdeb10f9ccb4a5799d2555cdbc2a068a42c0d Plaid: c32f22ffce5ec67c9e6147eaf6c4d7d5f8086d89 @@ -3348,7 +3295,7 @@ SPEC CHECKSUMS: RNLiveMarkdown: f19d3c962fba4fb87bb9bc27ce9119216d86d92e RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: 460d6ff97ae49c7d5708c3212c6521697c36a0c4 - RNPermissions: 9e5c26aaa982fe00743281f6f47fbdc050ebc58f + RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28 RNReactNativeHapticFeedback: 73756a3477a5a622fa16862a3ab0d0fc5e5edff5 RNReanimated: d95f865e1e42c34ca56b987e0719a8c72fc02dbc RNScreens: de6e57426ba0e6cbc3fb5b4f496e7f08cb2773c2 @@ -3364,6 +3311,6 @@ SPEC CHECKSUMS: VisionCamera: c95a8ad535f527562be1fb05fb2fd324578e769c Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8 -PODFILE CHECKSUM: e744fa802b4bee097ff8d1977dd8f79d16b21547 +PODFILE CHECKSUM: 615266329434ea4a994dccf622008a2197313c88 COCOAPODS: 1.15.2 diff --git a/modules/ContactsNitroModule/.gitignore b/modules/ContactsNitroModule/.gitignore deleted file mode 100644 index d3b53dfce541..000000000000 --- a/modules/ContactsNitroModule/.gitignore +++ /dev/null @@ -1,78 +0,0 @@ -# OSX -# -.DS_Store - -# XDE -.expo/ - -# VSCode -.vscode/ -jsconfig.json - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -project.xcworkspace - -# Android/IJ -# -.classpath -.cxx -.gradle -.idea -.project -.settings -local.properties -android.iml - -# Cocoapods -# -example/ios/Pods - -# Ruby -example/vendor/ - -# node.js -# -node_modules/ -npm-debug.log -yarn-debug.log -yarn-error.log - -# BUCK -buck-out/ -\.buckd/ -android/app/libs -android/keystores/debug.keystore - -# Yarn -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions - -# Expo -.expo/ - -# Turborepo -.turbo/ - -# generated by bob -lib/ diff --git a/modules/ContactsNitroModule/.watchmanconfig b/modules/ContactsNitroModule/.watchmanconfig deleted file mode 100644 index 0967ef424bce..000000000000 --- a/modules/ContactsNitroModule/.watchmanconfig +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/modules/ContactsNitroModule/ContactsModule.podspec b/modules/ContactsNitroModule/ContactsModule.podspec deleted file mode 100644 index 5f0b012c7b52..000000000000 --- a/modules/ContactsNitroModule/ContactsModule.podspec +++ /dev/null @@ -1,29 +0,0 @@ -require "json" - -package = JSON.parse(File.read(File.join(__dir__, "package.json"))) - -Pod::Spec.new do |s| - s.name = "ContactsModule" - s.version = package["version"] - s.summary = package["description"] - s.homepage = package["homepage"] - s.license = package["license"] - s.authors = package["author"] - - s.platforms = { :ios => min_ios_version_supported } - s.source = { :git => "https://github.com/mrousavy/nitro.git", :tag => "#{s.version}" } - - s.source_files = [ - # Implementation (Swift) - "ios/**/*.{swift}", - # Autolinking/Registration (Objective-C++) - "ios/**/*.{m,mm}", - # Implementation (C++ objects) - "cpp/**/*.{hpp,cpp}", - ] - - load 'nitrogen/generated/ios/ContactsModule+autolinking.rb' - add_nitrogen_files(s) - - install_modules_dependencies(s) -end diff --git a/modules/ContactsNitroModule/android/CMakeLists.txt b/modules/ContactsNitroModule/android/CMakeLists.txt deleted file mode 100644 index beb0c308df07..000000000000 --- a/modules/ContactsNitroModule/android/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -project(ContactsModule) -cmake_minimum_required(VERSION 3.9.0) - -set (PACKAGE_NAME ContactsModule) -set (CMAKE_VERBOSE_MAKEFILE ON) -set (CMAKE_CXX_STANDARD 20) - -# Define C++ library and add all sources -add_library(${PACKAGE_NAME} SHARED - src/main/cpp/cpp-adapter.cpp -) - -# Add Nitrogen specs :) -include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/ContactsModule+autolinking.cmake) - -# Set up local includes -include_directories( - "src/main/cpp" - "../cpp" -) - -find_library(LOG_LIB log) - -# Link all libraries together -target_link_libraries( - ${PACKAGE_NAME} - ${LOG_LIB} - android # <-- Android core -) diff --git a/modules/ContactsNitroModule/android/build.gradle b/modules/ContactsNitroModule/android/build.gradle deleted file mode 100644 index 0b414c88dea3..000000000000 --- a/modules/ContactsNitroModule/android/build.gradle +++ /dev/null @@ -1,130 +0,0 @@ -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath "com.android.tools.build:gradle:7.2.1" - } -} - -def reactNativeArchitectures() { - def value = rootProject.getProperties().get("reactNativeArchitectures") - return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] -} - -def isNewArchitectureEnabled() { - return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" -} - -apply plugin: "com.android.library" -apply plugin: 'org.jetbrains.kotlin.android' -apply from: '../nitrogen/generated/android/ContactsModule+autolinking.gradle' - -if (isNewArchitectureEnabled()) { - apply plugin: "com.facebook.react" -} - -def getExtOrDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["ContactsModule_" + name] -} - -def getExtOrIntegerDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ContactsModule_" + name]).toInteger() -} - -android { - namespace "com.margelo.nitro.contacts" - - ndkVersion getExtOrDefault("ndkVersion") - compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") - - defaultConfig { - minSdkVersion getExtOrIntegerDefault("minSdkVersion") - targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") - buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() - - externalNativeBuild { - cmake { - cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all" - arguments "-DANDROID_STL=c++_shared" - abiFilters (*reactNativeArchitectures()) - } - } - } - - externalNativeBuild { - cmake { - path "CMakeLists.txt" - } - } - - packagingOptions { - excludes = [ - "META-INF", - "META-INF/**", - "**/libc++_shared.so", - "**/libfbjni.so", - "**/libjsi.so", - "**/libfolly_json.so", - "**/libfolly_runtime.so", - "**/libglog.so", - "**/libhermes.so", - "**/libhermes-executor-debug.so", - "**/libhermes_executor.so", - "**/libreactnativejni.so", - "**/libturbomodulejsijni.so", - "**/libreact_nativemodule_core.so", - "**/libjscexecutor.so" - ] - } - - buildFeatures { - buildConfig true - prefab true - } - - buildTypes { - release { - minifyEnabled false - } - } - - lintOptions { - disable "GradleCompatible" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - sourceSets { - main { - if (isNewArchitectureEnabled()) { - java.srcDirs += [ - // React Codegen files - "${project.buildDir}/generated/source/codegen/java" - ] - } - } - } -} - -repositories { - mavenCentral() - google() -} - - -dependencies { - // For < 0.71, this will be from the local maven repo - // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin - //noinspection GradleDynamicVersion - implementation "com.facebook.react:react-native:+" - - // Add a dependency on NitroModules - implementation project(":react-native-nitro-modules") -} - diff --git a/modules/ContactsNitroModule/android/gradle.properties b/modules/ContactsNitroModule/android/gradle.properties deleted file mode 100644 index 59d3858d1bb9..000000000000 --- a/modules/ContactsNitroModule/android/gradle.properties +++ /dev/null @@ -1,5 +0,0 @@ -ContactsModule_kotlinVersion=1.9.24 -ContactsModule_minSdkVersion=23 -ContactsModule_targetSdkVersion=34 -ContactsModule_compileSdkVersion=34 -ContactsModule_ndkVersion=26.1.10909125 diff --git a/modules/ContactsNitroModule/android/src/main/AndroidManifest.xml b/modules/ContactsNitroModule/android/src/main/AndroidManifest.xml deleted file mode 100644 index a2f47b6057db..000000000000 --- a/modules/ContactsNitroModule/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/modules/ContactsNitroModule/android/src/main/cpp/cpp-adapter.cpp b/modules/ContactsNitroModule/android/src/main/cpp/cpp-adapter.cpp deleted file mode 100644 index 7a88410f3e4d..000000000000 --- a/modules/ContactsNitroModule/android/src/main/cpp/cpp-adapter.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include -#include "ContactsModuleOnLoad.hpp" - -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { - return margelo::nitro::contacts::initialize(vm); -} diff --git a/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/ContactsModulePackage.java b/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/ContactsModulePackage.java deleted file mode 100644 index e8c26844ce86..000000000000 --- a/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/ContactsModulePackage.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.margelo.nitro.contacts; - -import android.util.Log; - -import androidx.annotation.Nullable; - -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.module.model.ReactModuleInfoProvider; -import com.facebook.react.TurboReactPackage; -import com.margelo.nitro.core.HybridObject; -import com.margelo.nitro.core.HybridObjectRegistry; - -import java.util.HashMap; -import java.util.function.Supplier; - -public class ContactsModulePackage extends TurboReactPackage { - @Nullable - @Override - public NativeModule getModule(String name, ReactApplicationContext reactContext) { - return null; - } - - @Override - public ReactModuleInfoProvider getReactModuleInfoProvider() { - return () -> { - return new HashMap<>(); - }; - } - - static { - System.loadLibrary("ContactsModule"); - } -} diff --git a/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/HybridContactsModule.kt b/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/HybridContactsModule.kt deleted file mode 100644 index 00feaa7660c2..000000000000 --- a/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/HybridContactsModule.kt +++ /dev/null @@ -1,166 +0,0 @@ -package com.margelo.nitro.contacts - -import android.Manifest -import android.content.pm.PackageManager -import android.provider.ContactsContract -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import com.facebook.react.bridge.ReactApplicationContext -import com.margelo.nitro.NitroModules -import com.margelo.nitro.core.Promise - -class HybridContactsModule : HybridContactsModuleSpec() { - @Volatile - private var estimatedMemorySize: Long = 0 - - override val memorySize: Long - get() = estimatedMemorySize - - private val context: ReactApplicationContext? = NitroModules.applicationContext - - private fun requestContactPermission(): Boolean { - val currentActivity = context?.currentActivity - return if (currentActivity != null) { - ActivityCompat.requestPermissions( - currentActivity, arrayOf(REQUIRED_PERMISSION), PERMISSION_REQUEST_CODE - ) - true - } else { - false - } - } - - private fun hasPhoneContactsPermission(): Boolean { - return context?.let { - ContextCompat.checkSelfPermission(it, Manifest.permission.READ_CONTACTS) - } == PackageManager.PERMISSION_GRANTED - } - - override fun getAll(keys: Array): Promise> { - return Promise.parallel { - val contacts = mutableListOf() - if (!hasPhoneContactsPermission()) { - requestContactPermission() - return@parallel emptyArray() - } - - context?.contentResolver?.let { resolver -> - val projection = arrayOf( - ContactsContract.Data.MIMETYPE, - ContactsContract.Data.CONTACT_ID, - ContactsContract.Data.DISPLAY_NAME, - ContactsContract.Contacts.PHOTO_URI, - ContactsContract.Contacts.PHOTO_THUMBNAIL_URI, - ContactsContract.Data.DATA1, - ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, - ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, - ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME - ) - - val selection = "${ContactsContract.Data.MIMETYPE} IN (?, ?, ?)" - val selectionArgs = arrayOf( - ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, - ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, - ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE - ) - - val sortOrder = "${ContactsContract.Data.CONTACT_ID} ASC" - - resolver.query( - ContactsContract.Data.CONTENT_URI, - projection, - selection, - selectionArgs, - sortOrder - )?.use { cursor -> - val mimeTypeIndex = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE) - val contactIdIndex = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID) - val photoUriIndex = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI) - val thumbnailUriIndex = - cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI) - val data1Index = cursor.getColumnIndex(ContactsContract.Data.DATA1) - val givenNameIndex = - cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME) - val familyNameIndex = - cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME) - val middleNameIndex = - cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME) - - var currentContact: Contact? = null - var currentContactId: String? = null - val currentPhoneNumbers = mutableListOf() - val currentEmailAddresses = mutableListOf() - - while (cursor.moveToNext()) { - val contactId = cursor.getString(contactIdIndex) - val mimeType = cursor.getString(mimeTypeIndex) - - if (contactId != currentContactId) { - currentContact?.let { contact -> - contacts.add( - contact.copy( - phoneNumbers = currentPhoneNumbers.toTypedArray(), - emailAddresses = currentEmailAddresses.toTypedArray() - ) - ) - } - currentPhoneNumbers.clear() - currentEmailAddresses.clear() - currentContact = Contact( - firstName = "", - lastName = "", - middleName = null, - phoneNumbers = emptyArray(), - emailAddresses = emptyArray(), - imageData = cursor.getString(photoUriIndex) ?: "", - thumbnailImageData = cursor.getString(thumbnailUriIndex) ?: "" - ) - currentContactId = contactId - } - - when (mimeType) { - ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> { - currentContact = currentContact?.copy( - firstName = cursor.getString(givenNameIndex) ?: "", - lastName = cursor.getString(familyNameIndex) ?: "", - middleName = cursor.getString(middleNameIndex) - ) - } - - ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> { - cursor.getString(data1Index)?.let { phone -> - currentPhoneNumbers.add(StringHolder(phone)) - } - } - - ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> { - cursor.getString(data1Index)?.let { email -> - currentEmailAddresses.add(StringHolder(email)) - } - } - } - } - - // Add the last contact - currentContact?.let { contact -> - contacts.add( - contact.copy( - phoneNumbers = currentPhoneNumbers.toTypedArray(), - emailAddresses = currentEmailAddresses.toTypedArray() - ) - ) - } - } - } - - // Update memory size based on contact count - estimatedMemorySize = contacts.size.toLong() * 1024 // Assume ~1KB per contact - contacts.toTypedArray() - } - } - - companion object { - const val PERMISSION_REQUEST_CODE = 1 - const val REQUIRED_PERMISSION = Manifest.permission.READ_CONTACTS - } -} diff --git a/modules/ContactsNitroModule/babel.config.js b/modules/ContactsNitroModule/babel.config.js deleted file mode 100644 index 3e0218e68fc3..000000000000 --- a/modules/ContactsNitroModule/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: ['module:@react-native/babel-preset'], -} diff --git a/modules/ContactsNitroModule/ios/HybridContactsModule.swift b/modules/ContactsNitroModule/ios/HybridContactsModule.swift deleted file mode 100644 index 59cc3ea31702..000000000000 --- a/modules/ContactsNitroModule/ios/HybridContactsModule.swift +++ /dev/null @@ -1,72 +0,0 @@ -import NitroModules -import Contacts -import Foundation - -final class HybridContactsModule: HybridContactsModuleSpec { - public var hybridContext = margelo.nitro.HybridContext() - public var memorySize: Int { MemoryLayout.size } - - private let contactStore = CNContactStore() - private let imageDirectory: URL - private let fieldToKeyDescriptor: [ContactFields: CNKeyDescriptor] = [ - .firstName: CNContactGivenNameKey as CNKeyDescriptor, - .lastName: CNContactFamilyNameKey as CNKeyDescriptor, - .phoneNumbers: CNContactPhoneNumbersKey as CNKeyDescriptor, - .emailAddresses: CNContactEmailAddressesKey as CNKeyDescriptor, - .middleName: CNContactMiddleNameKey as CNKeyDescriptor, - .imageData: CNContactImageDataKey as CNKeyDescriptor, - .thumbnailImageData: CNContactThumbnailImageDataKey as CNKeyDescriptor, - .givenNameKey: CNContactGivenNameKey as CNKeyDescriptor - ] - - init() { - imageDirectory = FileManager.default.temporaryDirectory.appendingPathComponent("ContactImages") - try? FileManager.default.createDirectory(at: imageDirectory, withIntermediateDirectories: true) - } - - func getAll(keys: [ContactFields]) throws -> Promise<[Contact]> { - Promise.async { [unowned self] in - let keysSet = Set(keys) - let keysToFetch = keys.compactMap { self.fieldToKeyDescriptor[$0] } - guard !keysToFetch.isEmpty else { return [] } - - let request = CNContactFetchRequest(keysToFetch: keysToFetch) - var contacts = [Contact]() - contacts.reserveCapacity(1000) - - try self.contactStore.enumerateContacts(with: request) { contact, _ in - contacts.append(self.processContact(contact, keysSet: keysSet)) - } - - return contacts - } - } - - @inline(__always) - private func processContact(_ contact: CNContact, keysSet: Set) -> Contact { - Contact( - firstName: keysSet.contains(.firstName) ? contact.givenName : nil, - lastName: keysSet.contains(.lastName) ? contact.familyName : nil, - middleName: keysSet.contains(.middleName) ? contact.middleName : nil, - phoneNumbers: keysSet.contains(.phoneNumbers) ? contact.phoneNumbers.map { StringHolder(value: $0.value.stringValue) } : nil, - emailAddresses: keysSet.contains(.emailAddresses) ? contact.emailAddresses.map { StringHolder(value: $0.value as String) } : nil, - imageData: keysSet.contains(.imageData) ? getImagePath(for: contact, isThumbnail: false) : nil, - thumbnailImageData: keysSet.contains(.thumbnailImageData) ? getImagePath(for: contact, isThumbnail: true) : nil - ) - } - - @inline(__always) - private func getImagePath(for contact: CNContact, isThumbnail: Bool) -> String? { - let imageData = isThumbnail ? contact.thumbnailImageData : contact.imageData - guard let data = imageData else { return nil } - - let fileName = "\(contact.identifier)_\(isThumbnail ? "thumb" : "full").jpg" - let fileURL = imageDirectory.appendingPathComponent(fileName) - - if !FileManager.default.fileExists(atPath: fileURL.path) { - try? data.write(to: fileURL, options: .atomic) - } - - return fileURL.path - } -} diff --git a/modules/ContactsNitroModule/nitro.json b/modules/ContactsNitroModule/nitro.json deleted file mode 100644 index 426f8486118a..000000000000 --- a/modules/ContactsNitroModule/nitro.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "cxxNamespace": ["contacts"], - "ios": { - "iosModuleName": "ContactsModule" - }, - "android": { - "androidNamespace": ["contacts"], - "androidCxxLibName": "ContactsModule" - }, - "autolinking": { - "ContactsModule": { - "swift": "HybridContactsModule", - "kotlin": "HybridContactsModule" - } - }, - "ignorePaths": ["node_modules"] -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.cmake b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.cmake deleted file mode 100644 index 5478bc224b05..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.cmake +++ /dev/null @@ -1,59 +0,0 @@ -# -# ContactsModule+autolinking.cmake -# This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -# https://github.com/mrousavy/nitro -# Copyright © 2024 Marc Rousavy @ Margelo -# - -# This is a CMake file that adds all files generated by Nitrogen -# to the current CMake project. -# -# To use it, add this to your CMakeLists.txt: -# ```cmake -# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/ContactsModule+autolinking.cmake) -# ``` - -# Add all headers that were generated by Nitrogen -include_directories( - "../nitrogen/generated/shared/c++" - "../nitrogen/generated/android/c++" - "../nitrogen/generated/android/" -) - -# Add all .cpp sources that were generated by Nitrogen -target_sources( - # CMake project name (Android C++ library name) - ContactsModule PRIVATE - # Autolinking Setup - ../nitrogen/generated/android/ContactsModuleOnLoad.cpp - # Shared Nitrogen C++ sources - ../nitrogen/generated/shared/c++/HybridContactsModuleSpec.cpp - # Android-specific Nitrogen C++ sources - ../nitrogen/generated/android/c++/JHybridContactsModuleSpec.cpp -) - -# Add all libraries required by the generated specs -find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++ -find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule) -find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library - -# Link all libraries together -target_link_libraries( - ContactsModule - fbjni::fbjni # <-- Facebook C++ JNI helpers - ReactAndroid::jsi # <-- RN: JSI - react-native-nitro-modules::NitroModules # <-- NitroModules Core :) -) - -# Link react-native (different prefab between RN 0.75 and RN 0.76) -if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76) - target_link_libraries( - ContactsModule - ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab - ) -else() - target_link_libraries( - ContactsModule - ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core - ) -endif() diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.gradle b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.gradle deleted file mode 100644 index 2d19cd2ced32..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.gradle +++ /dev/null @@ -1,27 +0,0 @@ -/// -/// ContactsModule+autolinking.gradle -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -/// This is a Gradle file that adds all files generated by Nitrogen -/// to the current Gradle project. -/// -/// To use it, add this to your build.gradle: -/// ```gradle -/// apply from: '../nitrogen/generated/android/ContactsModule+autolinking.gradle' -/// ``` - -logger.warn("[NitroModules] 🔥 ContactsModule is boosted by nitro!") - -android { - sourceSets { - main { - java.srcDirs += [ - // Nitrogen files - "${project.projectDir}/../nitrogen/generated/android/kotlin" - ] - } - } -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.cpp b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.cpp deleted file mode 100644 index 156ea811e509..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/// -/// ContactsModuleOnLoad.cpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#include "ContactsModuleOnLoad.hpp" - -#include -#include -#include - -#include "JHybridContactsModuleSpec.hpp" -#include -#include - -namespace margelo::nitro::contacts { - -int initialize(JavaVM* vm) { - using namespace margelo::nitro; - using namespace margelo::nitro::contacts; - using namespace facebook; - - return facebook::jni::initialize(vm, [] { - // Register native JNI methods - margelo::nitro::contacts::JHybridContactsModuleSpec::registerNatives(); - - // Register Nitro Hybrid Objects - HybridObjectRegistry::registerHybridObjectConstructor( - "ContactsModule", - []() -> std::shared_ptr { - static DefaultConstructableObject object("com/margelo/nitro/contacts/HybridContactsModule"); - auto instance = object.create(); - auto globalRef = jni::make_global(instance); - return JNISharedPtr::make_shared_from_jni(globalRef); - } - ); - }); -} - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.hpp deleted file mode 100644 index b71adaca07bf..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/// -/// ContactsModuleOnLoad.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#include -#include - -namespace margelo::nitro::contacts { - - /** - * Initializes the native (C++) part of ContactsModule, and autolinks all Hybrid Objects. - * Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`). - * Example: - * ```cpp (cpp-adapter.cpp) - * JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { - * return margelo::nitro::contacts::initialize(vm); - * } - * ``` - */ - int initialize(JavaVM* vm); - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.kt b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.kt deleted file mode 100644 index 8b137891791f..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.kt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContact.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContact.hpp deleted file mode 100644 index bbd5354163a2..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContact.hpp +++ /dev/null @@ -1,114 +0,0 @@ -/// -/// JContact.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -#include -#include "Contact.hpp" - -#include "JStringHolder.hpp" -#include "StringHolder.hpp" -#include -#include -#include - -namespace margelo::nitro::contacts { - - using namespace facebook; - - /** - * The C++ JNI bridge between the C++ struct "Contact" and the the Kotlin data class "Contact". - */ - struct JContact final: public jni::JavaClass { - public: - static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/contacts/Contact;"; - - public: - /** - * Convert this Java/Kotlin-based struct to the C++ struct Contact by copying all values to C++. - */ - [[maybe_unused]] - Contact toCpp() const { - static const auto clazz = javaClassStatic(); - static const auto fieldFirstName = clazz->getField("firstName"); - jni::local_ref firstName = this->getFieldValue(fieldFirstName); - static const auto fieldLastName = clazz->getField("lastName"); - jni::local_ref lastName = this->getFieldValue(fieldLastName); - static const auto fieldMiddleName = clazz->getField("middleName"); - jni::local_ref middleName = this->getFieldValue(fieldMiddleName); - static const auto fieldPhoneNumbers = clazz->getField>("phoneNumbers"); - jni::local_ref> phoneNumbers = this->getFieldValue(fieldPhoneNumbers); - static const auto fieldEmailAddresses = clazz->getField>("emailAddresses"); - jni::local_ref> emailAddresses = this->getFieldValue(fieldEmailAddresses); - static const auto fieldImageData = clazz->getField("imageData"); - jni::local_ref imageData = this->getFieldValue(fieldImageData); - static const auto fieldThumbnailImageData = clazz->getField("thumbnailImageData"); - jni::local_ref thumbnailImageData = this->getFieldValue(fieldThumbnailImageData); - return Contact( - firstName != nullptr ? std::make_optional(firstName->toStdString()) : std::nullopt, - lastName != nullptr ? std::make_optional(lastName->toStdString()) : std::nullopt, - middleName != nullptr ? std::make_optional(middleName->toStdString()) : std::nullopt, - phoneNumbers != nullptr ? std::make_optional([&]() { - size_t __size = phoneNumbers->size(); - std::vector __vector; - __vector.reserve(__size); - for (size_t __i = 0; __i < __size; __i++) { - auto __element = phoneNumbers->getElement(__i); - __vector.push_back(__element->toCpp()); - } - return __vector; - }()) : std::nullopt, - emailAddresses != nullptr ? std::make_optional([&]() { - size_t __size = emailAddresses->size(); - std::vector __vector; - __vector.reserve(__size); - for (size_t __i = 0; __i < __size; __i++) { - auto __element = emailAddresses->getElement(__i); - __vector.push_back(__element->toCpp()); - } - return __vector; - }()) : std::nullopt, - imageData != nullptr ? std::make_optional(imageData->toStdString()) : std::nullopt, - thumbnailImageData != nullptr ? std::make_optional(thumbnailImageData->toStdString()) : std::nullopt - ); - } - - public: - /** - * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. - */ - [[maybe_unused]] - static jni::local_ref fromCpp(const Contact& value) { - return newInstance( - value.firstName.has_value() ? jni::make_jstring(value.firstName.value()) : nullptr, - value.lastName.has_value() ? jni::make_jstring(value.lastName.value()) : nullptr, - value.middleName.has_value() ? jni::make_jstring(value.middleName.value()) : nullptr, - value.phoneNumbers.has_value() ? [&]() { - size_t __size = value.phoneNumbers.value().size(); - jni::local_ref> __array = jni::JArrayClass::newArray(__size); - for (size_t __i = 0; __i < __size; __i++) { - const auto& __element = value.phoneNumbers.value()[__i]; - __array->setElement(__i, *JStringHolder::fromCpp(__element)); - } - return __array; - }() : nullptr, - value.emailAddresses.has_value() ? [&]() { - size_t __size = value.emailAddresses.value().size(); - jni::local_ref> __array = jni::JArrayClass::newArray(__size); - for (size_t __i = 0; __i < __size; __i++) { - const auto& __element = value.emailAddresses.value()[__i]; - __array->setElement(__i, *JStringHolder::fromCpp(__element)); - } - return __array; - }() : nullptr, - value.imageData.has_value() ? jni::make_jstring(value.imageData.value()) : nullptr, - value.thumbnailImageData.has_value() ? jni::make_jstring(value.thumbnailImageData.value()) : nullptr - ); - } - }; - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContactFields.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContactFields.hpp deleted file mode 100644 index 371b6607d105..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContactFields.hpp +++ /dev/null @@ -1,76 +0,0 @@ -/// -/// JContactFields.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -#include -#include "ContactFields.hpp" - -namespace margelo::nitro::contacts { - - using namespace facebook; - - /** - * The C++ JNI bridge between the C++ enum "ContactFields" and the the Kotlin enum "ContactFields". - */ - struct JContactFields final: public jni::JavaClass { - public: - static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/contacts/ContactFields;"; - - public: - /** - * Convert this Java/Kotlin-based enum to the C++ enum ContactFields. - */ - [[maybe_unused]] - ContactFields toCpp() const { - static const auto clazz = javaClassStatic(); - static const auto fieldOrdinal = clazz->getField("_ordinal"); - int ordinal = this->getFieldValue(fieldOrdinal); - return static_cast(ordinal); - } - - public: - /** - * Create a Java/Kotlin-based enum with the given C++ enum's value. - */ - [[maybe_unused]] - static jni::alias_ref fromCpp(ContactFields value) { - static const auto clazz = javaClassStatic(); - static const auto fieldFIRST_NAME = clazz->getStaticField("FIRST_NAME"); - static const auto fieldLAST_NAME = clazz->getStaticField("LAST_NAME"); - static const auto fieldMIDDLE_NAME = clazz->getStaticField("MIDDLE_NAME"); - static const auto fieldPHONE_NUMBERS = clazz->getStaticField("PHONE_NUMBERS"); - static const auto fieldEMAIL_ADDRESSES = clazz->getStaticField("EMAIL_ADDRESSES"); - static const auto fieldIMAGE_DATA = clazz->getStaticField("IMAGE_DATA"); - static const auto fieldTHUMBNAIL_IMAGE_DATA = clazz->getStaticField("THUMBNAIL_IMAGE_DATA"); - static const auto fieldGIVEN_NAME_KEY = clazz->getStaticField("GIVEN_NAME_KEY"); - - switch (value) { - case ContactFields::FIRST_NAME: - return clazz->getStaticFieldValue(fieldFIRST_NAME); - case ContactFields::LAST_NAME: - return clazz->getStaticFieldValue(fieldLAST_NAME); - case ContactFields::MIDDLE_NAME: - return clazz->getStaticFieldValue(fieldMIDDLE_NAME); - case ContactFields::PHONE_NUMBERS: - return clazz->getStaticFieldValue(fieldPHONE_NUMBERS); - case ContactFields::EMAIL_ADDRESSES: - return clazz->getStaticFieldValue(fieldEMAIL_ADDRESSES); - case ContactFields::IMAGE_DATA: - return clazz->getStaticFieldValue(fieldIMAGE_DATA); - case ContactFields::THUMBNAIL_IMAGE_DATA: - return clazz->getStaticFieldValue(fieldTHUMBNAIL_IMAGE_DATA); - case ContactFields::GIVEN_NAME_KEY: - return clazz->getStaticFieldValue(fieldGIVEN_NAME_KEY); - default: - std::string stringValue = std::to_string(static_cast(value)); - throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); - } - } - }; - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.cpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.cpp deleted file mode 100644 index e0505ee46d36..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/// -/// JHybridContactsModuleSpec.cpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#include "JHybridContactsModuleSpec.hpp" - -// Forward declaration of `Contact` to properly resolve imports. -namespace margelo::nitro::contacts { struct Contact; } -// Forward declaration of `StringHolder` to properly resolve imports. -namespace margelo::nitro::contacts { struct StringHolder; } -// Forward declaration of `ContactFields` to properly resolve imports. -namespace margelo::nitro::contacts { enum class ContactFields; } - -#include -#include -#include "Contact.hpp" -#include -#include "JContact.hpp" -#include -#include -#include "StringHolder.hpp" -#include "JStringHolder.hpp" -#include "ContactFields.hpp" -#include "JContactFields.hpp" - -namespace margelo::nitro::contacts { - - jni::local_ref JHybridContactsModuleSpec::initHybrid(jni::alias_ref jThis) { - return makeCxxInstance(jThis); - } - - void JHybridContactsModuleSpec::registerNatives() { - registerHybrid({ - makeNativeMethod("initHybrid", JHybridContactsModuleSpec::initHybrid), - }); - } - - size_t JHybridContactsModuleSpec::getExternalMemorySize() noexcept { - static const auto method = _javaPart->getClass()->getMethod("getMemorySize"); - return method(_javaPart); - } - - // Properties - - - // Methods - std::shared_ptr>> JHybridContactsModuleSpec::getAll(const std::vector& keys) { - static const auto method = _javaPart->getClass()->getMethod(jni::alias_ref> /* keys */)>("getAll"); - auto __result = method(_javaPart, [&]() { - size_t __size = keys.size(); - jni::local_ref> __array = jni::JArrayClass::newArray(__size); - for (size_t __i = 0; __i < __size; __i++) { - const auto& __element = keys[__i]; - __array->setElement(__i, *JContactFields::fromCpp(__element)); - } - return __array; - }()); - return [&]() { - auto __promise = Promise>::create(); - __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { - auto __result = jni::static_ref_cast>(__boxedResult); - __promise->resolve([&]() { - size_t __size = __result->size(); - std::vector __vector; - __vector.reserve(__size); - for (size_t __i = 0; __i < __size; __i++) { - auto __element = __result->getElement(__i); - __vector.push_back(__element->toCpp()); - } - return __vector; - }()); - }); - __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) { - jni::JniException __jniError(__throwable); - __promise->reject(std::make_exception_ptr(__jniError)); - }); - return __promise; - }(); - } - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.hpp deleted file mode 100644 index 6b94d3be37e7..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/// -/// HybridContactsModuleSpec.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -#include -#include -#include "HybridContactsModuleSpec.hpp" - - - - -namespace margelo::nitro::contacts { - - using namespace facebook; - - class JHybridContactsModuleSpec: public jni::HybridClass, - public virtual HybridContactsModuleSpec { - public: - static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/contacts/HybridContactsModuleSpec;"; - static jni::local_ref initHybrid(jni::alias_ref jThis); - static void registerNatives(); - - protected: - // C++ constructor (called from Java via `initHybrid()`) - explicit JHybridContactsModuleSpec(jni::alias_ref jThis) : - HybridObject(HybridContactsModuleSpec::TAG), - _javaPart(jni::make_global(jThis)) {} - - public: - virtual ~JHybridContactsModuleSpec() { - // Hermes GC can destroy JS objects on a non-JNI Thread. - jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); }); - } - - public: - size_t getExternalMemorySize() noexcept override; - - public: - inline const jni::global_ref& getJavaPart() const noexcept { - return _javaPart; - } - - public: - // Properties - - - public: - // Methods - std::shared_ptr>> getAll(const std::vector& keys) override; - - private: - friend HybridBase; - using HybridBase::HybridBase; - jni::global_ref _javaPart; - }; - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JStringHolder.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JStringHolder.hpp deleted file mode 100644 index 29695fe48d58..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JStringHolder.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/// -/// JStringHolder.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -#include -#include "StringHolder.hpp" - -#include - -namespace margelo::nitro::contacts { - - using namespace facebook; - - /** - * The C++ JNI bridge between the C++ struct "StringHolder" and the the Kotlin data class "StringHolder". - */ - struct JStringHolder final: public jni::JavaClass { - public: - static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/contacts/StringHolder;"; - - public: - /** - * Convert this Java/Kotlin-based struct to the C++ struct StringHolder by copying all values to C++. - */ - [[maybe_unused]] - StringHolder toCpp() const { - static const auto clazz = javaClassStatic(); - static const auto fieldValue = clazz->getField("value"); - jni::local_ref value = this->getFieldValue(fieldValue); - return StringHolder( - value->toStdString() - ); - } - - public: - /** - * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. - */ - [[maybe_unused]] - static jni::local_ref fromCpp(const StringHolder& value) { - return newInstance( - jni::make_jstring(value.value) - ); - } - }; - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/Contact.kt b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/Contact.kt deleted file mode 100644 index a6d9e59a2b2b..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/Contact.kt +++ /dev/null @@ -1,27 +0,0 @@ -/// -/// Contact.kt -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -package com.margelo.nitro.contacts - -import androidx.annotation.Keep -import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* - -/** - * Represents the JavaScript object/struct "Contact". - */ -@DoNotStrip -@Keep -data class Contact( - val firstName: String?, - val lastName: String?, - val middleName: String?, - val phoneNumbers: Array?, - val emailAddresses: Array?, - val imageData: String?, - val thumbnailImageData: String? -) diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/ContactFields.kt b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/ContactFields.kt deleted file mode 100644 index 841d6c82a32b..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/ContactFields.kt +++ /dev/null @@ -1,31 +0,0 @@ -/// -/// ContactFields.kt -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -package com.margelo.nitro.contacts - -import androidx.annotation.Keep -import com.facebook.proguard.annotations.DoNotStrip - -/** - * Represents the JavaScript enum/union "ContactFields". - */ -@DoNotStrip -@Keep -enum class ContactFields { - FIRST_NAME, - LAST_NAME, - MIDDLE_NAME, - PHONE_NUMBERS, - EMAIL_ADDRESSES, - IMAGE_DATA, - THUMBNAIL_IMAGE_DATA, - GIVEN_NAME_KEY; - - @DoNotStrip - @Keep - private val _ordinal = ordinal -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/HybridContactsModuleSpec.kt b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/HybridContactsModuleSpec.kt deleted file mode 100644 index 63a118b8be57..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/HybridContactsModuleSpec.kt +++ /dev/null @@ -1,64 +0,0 @@ -/// -/// HybridContactsModuleSpec.kt -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -package com.margelo.nitro.contacts - -import android.util.Log -import androidx.annotation.Keep -import com.facebook.jni.HybridData -import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* - -/** - * A Kotlin class representing the ContactsModule HybridObject. - * Implement this abstract class to create Kotlin-based instances of ContactsModule. - */ -@DoNotStrip -@Keep -@Suppress("RedundantSuppression", "KotlinJniMissingFunction", "PropertyName", "RedundantUnitReturnType", "unused") -abstract class HybridContactsModuleSpec: HybridObject() { - @DoNotStrip - private var mHybridData: HybridData = initHybrid() - - init { - // Pass this `HybridData` through to it's base class, - // to represent inheritance to JHybridObject on C++ side - super.updateNative(mHybridData) - } - - /** - * Call from a child class to initialize HybridData with a child. - */ - override fun updateNative(hybridData: HybridData) { - mHybridData = hybridData - } - - // Properties - - - // Methods - @DoNotStrip - @Keep - abstract fun getAll(keys: Array): Promise> - - private external fun initHybrid(): HybridData - - companion object { - private const val TAG = "HybridContactsModuleSpec" - init { - try { - Log.i(TAG, "Loading ContactsModule C++ library...") - System.loadLibrary("ContactsModule") - Log.i(TAG, "Successfully loaded ContactsModule C++ library!") - } catch (e: Error) { - Log.e(TAG, "Failed to load ContactsModule C++ library! Is it properly installed and linked? " + - "Is the name correct? (see `CMakeLists.txt`, at `add_library(...)`)", e) - throw e - } - } - } -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/StringHolder.kt b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/StringHolder.kt deleted file mode 100644 index b6af53e53217..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/StringHolder.kt +++ /dev/null @@ -1,21 +0,0 @@ -/// -/// StringHolder.kt -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -package com.margelo.nitro.contacts - -import androidx.annotation.Keep -import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* - -/** - * Represents the JavaScript object/struct "StringHolder". - */ -@DoNotStrip -@Keep -data class StringHolder( - val value: String -) diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule+autolinking.rb b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule+autolinking.rb deleted file mode 100644 index 35bc19c47bf7..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule+autolinking.rb +++ /dev/null @@ -1,58 +0,0 @@ -# -# ContactsModule+autolinking.rb -# This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -# https://github.com/mrousavy/nitro -# Copyright © 2024 Marc Rousavy @ Margelo -# - -# This is a Ruby script that adds all files generated by Nitrogen -# to the given podspec. -# -# To use it, add this to your .podspec: -# ```ruby -# Pod::Spec.new do |spec| -# # ... -# -# # Add all files generated by Nitrogen -# load 'nitrogen/generated/ios/ContactsModule+autolinking.rb' -# add_nitrogen_files(spec) -# end -# ``` - -def add_nitrogen_files(spec) - Pod::UI.puts "[NitroModules] 🔥 ContactsModule is boosted by nitro!" - - spec.dependency "NitroModules" - - current_source_files = Array(spec.attributes_hash['source_files']) - spec.source_files = current_source_files + [ - # Generated cross-platform specs - "nitrogen/generated/shared/**/*.{h,hpp,c,cpp,swift}", - # Generated bridges for the cross-platform specs - "nitrogen/generated/ios/**/*.{h,hpp,c,cpp,mm,swift}", - ] - - current_public_header_files = Array(spec.attributes_hash['public_header_files']) - spec.public_header_files = current_public_header_files + [ - # Generated specs - "nitrogen/generated/shared/**/*.{h,hpp}", - # Swift to C++ bridging helpers - "nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.hpp" - ] - - current_private_header_files = Array(spec.attributes_hash['private_header_files']) - spec.private_header_files = current_private_header_files + [ - # iOS specific specs - "nitrogen/generated/ios/c++/**/*.{h,hpp}", - ] - - current_pod_target_xcconfig = spec.attributes_hash['pod_target_xcconfig'] || {} - spec.pod_target_xcconfig = current_pod_target_xcconfig.merge({ - # Use C++ 20 - "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", - # Enables C++ <-> Swift interop (by default it's only C) - "SWIFT_OBJC_INTEROP_MODE" => "objcxx", - # Enables stricter modular headers - "DEFINES_MODULE" => "YES", - }) -end diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.cpp b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.cpp deleted file mode 100644 index 4746dbadaa18..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/// -/// ContactsModule-Swift-Cxx-Bridge.cpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#include "ContactsModule-Swift-Cxx-Bridge.hpp" - -// Include C++ implementation defined types -#include "ContactsModule-Swift-Cxx-Umbrella.hpp" -#include "HybridContactsModuleSpecSwift.hpp" -#include - -namespace margelo::nitro::contacts::bridge::swift { - - // pragma MARK: std::shared_ptr - std::shared_ptr create_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(void* _Nonnull swiftUnsafePointer) { - ContactsModule::HybridContactsModuleSpecCxx swiftPart = ContactsModule::HybridContactsModuleSpecCxxUnsafe::fromUnsafe(swiftUnsafePointer); - return HybridContext::getOrCreate(swiftPart); - } - void* _Nonnull get_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ cppType) { - std::shared_ptr swiftWrapper = std::dynamic_pointer_cast(cppType); - #ifdef NITRO_DEBUG - if (swiftWrapper == nullptr) [[unlikely]] { - throw std::runtime_error("Class \"HybridContactsModuleSpec\" is not implemented in Swift!"); - } - #endif - ContactsModule::HybridContactsModuleSpecCxx swiftPart = swiftWrapper->getSwiftPart(); - return ContactsModule::HybridContactsModuleSpecCxxUnsafe::toUnsafe(swiftPart); - } - -} // namespace margelo::nitro::contacts::bridge::swift diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.hpp b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.hpp deleted file mode 100644 index 76d584613df2..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.hpp +++ /dev/null @@ -1,167 +0,0 @@ -/// -/// ContactsModule-Swift-Cxx-Bridge.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -// Forward declarations of C++ defined types -// Forward declaration of `ContactFields` to properly resolve imports. -namespace margelo::nitro::contacts { enum class ContactFields; } -// Forward declaration of `Contact` to properly resolve imports. -namespace margelo::nitro::contacts { struct Contact; } -// Forward declaration of `HybridContactsModuleSpec` to properly resolve imports. -namespace margelo::nitro::contacts { class HybridContactsModuleSpec; } -// Forward declaration of `StringHolder` to properly resolve imports. -namespace margelo::nitro::contacts { struct StringHolder; } - -// Forward declarations of Swift defined types -// Forward declaration of `HybridContactsModuleSpecCxx` to properly resolve imports. -namespace ContactsModule { class HybridContactsModuleSpecCxx; } - -// Include C++ defined types -#include "Contact.hpp" -#include "ContactFields.hpp" -#include "HybridContactsModuleSpec.hpp" -#include "StringHolder.hpp" -#include -#include -#include -#include -#include -#include -#include - -/** - * Contains specialized versions of C++ templated types so they can be accessed from Swift, - * as well as helper functions to interact with those C++ types from Swift. - */ -namespace margelo::nitro::contacts::bridge::swift { - - // pragma MARK: std::optional - /** - * Specialized version of `std::optional`. - */ - using std__optional_std__string_ = std::optional; - inline std::optional create_std__optional_std__string_(const std::string& value) { - return std::optional(value); - } - - // pragma MARK: std::vector - /** - * Specialized version of `std::vector`. - */ - using std__vector_StringHolder_ = std::vector; - inline std::vector create_std__vector_StringHolder_(size_t size) { - std::vector vector; - vector.reserve(size); - return vector; - } - - // pragma MARK: std::optional> - /** - * Specialized version of `std::optional>`. - */ - using std__optional_std__vector_StringHolder__ = std::optional>; - inline std::optional> create_std__optional_std__vector_StringHolder__(const std::vector& value) { - return std::optional>(value); - } - - // pragma MARK: std::vector - /** - * Specialized version of `std::vector`. - */ - using std__vector_Contact_ = std::vector; - inline std::vector create_std__vector_Contact_(size_t size) { - std::vector vector; - vector.reserve(size); - return vector; - } - - // pragma MARK: std::shared_ptr>> - /** - * Specialized version of `std::shared_ptr>>`. - */ - using std__shared_ptr_Promise_std__vector_Contact___ = std::shared_ptr>>; - inline std::shared_ptr>> create_std__shared_ptr_Promise_std__vector_Contact___() { - return Promise>::create(); - } - - // pragma MARK: std::function& /* result */)> - /** - * Specialized version of `std::function&)>`. - */ - using Func_void_std__vector_Contact_ = std::function& /* result */)>; - /** - * Wrapper class for a `std::function& / * result * /)>`, this can be used from Swift. - */ - class Func_void_std__vector_Contact__Wrapper final { - public: - explicit Func_void_std__vector_Contact__Wrapper(const std::function& /* result */)>& func): _function(func) {} - explicit Func_void_std__vector_Contact__Wrapper(std::function& /* result */)>&& func): _function(std::move(func)) {} - inline void call(std::vector result) const { - _function(result); - } - private: - std::function& /* result */)> _function; - }; - inline Func_void_std__vector_Contact_ create_Func_void_std__vector_Contact_(void* _Nonnull closureHolder, void(* _Nonnull call)(void* _Nonnull /* closureHolder */, std::vector), void(* _Nonnull destroy)(void* _Nonnull)) { - std::shared_ptr sharedClosureHolder(closureHolder, destroy); - return Func_void_std__vector_Contact_([sharedClosureHolder, call](const std::vector& result) -> void { - call(sharedClosureHolder.get(), result); - }); - } - inline std::shared_ptr share_Func_void_std__vector_Contact_(const Func_void_std__vector_Contact_& value) { - return std::make_shared(value); - } - - // pragma MARK: std::function - /** - * Specialized version of `std::function`. - */ - using Func_void_std__exception_ptr = std::function; - /** - * Wrapper class for a `std::function`, this can be used from Swift. - */ - class Func_void_std__exception_ptr_Wrapper final { - public: - explicit Func_void_std__exception_ptr_Wrapper(const std::function& func): _function(func) {} - explicit Func_void_std__exception_ptr_Wrapper(std::function&& func): _function(std::move(func)) {} - inline void call(std::exception_ptr error) const { - _function(error); - } - private: - std::function _function; - }; - inline Func_void_std__exception_ptr create_Func_void_std__exception_ptr(void* _Nonnull closureHolder, void(* _Nonnull call)(void* _Nonnull /* closureHolder */, std::exception_ptr), void(* _Nonnull destroy)(void* _Nonnull)) { - std::shared_ptr sharedClosureHolder(closureHolder, destroy); - return Func_void_std__exception_ptr([sharedClosureHolder, call](const std::exception_ptr& error) -> void { - call(sharedClosureHolder.get(), error); - }); - } - inline std::shared_ptr share_Func_void_std__exception_ptr(const Func_void_std__exception_ptr& value) { - return std::make_shared(value); - } - - // pragma MARK: std::vector - /** - * Specialized version of `std::vector`. - */ - using std__vector_ContactFields_ = std::vector; - inline std::vector create_std__vector_ContactFields_(size_t size) { - std::vector vector; - vector.reserve(size); - return vector; - } - - // pragma MARK: std::shared_ptr - /** - * Specialized version of `std::shared_ptr`. - */ - using std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ = std::shared_ptr; - std::shared_ptr create_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(void* _Nonnull swiftUnsafePointer); - void* _Nonnull get_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ cppType); - -} // namespace margelo::nitro::contacts::bridge::swift diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Umbrella.hpp b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Umbrella.hpp deleted file mode 100644 index 6f38d7c7e417..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Umbrella.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/// -/// ContactsModule-Swift-Cxx-Umbrella.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -// Forward declarations of C++ defined types -// Forward declaration of `ContactFields` to properly resolve imports. -namespace margelo::nitro::contacts { enum class ContactFields; } -// Forward declaration of `Contact` to properly resolve imports. -namespace margelo::nitro::contacts { struct Contact; } -// Forward declaration of `HybridContactsModuleSpec` to properly resolve imports. -namespace margelo::nitro::contacts { class HybridContactsModuleSpec; } -// Forward declaration of `StringHolder` to properly resolve imports. -namespace margelo::nitro::contacts { struct StringHolder; } - -// Include C++ defined types -#include "Contact.hpp" -#include "ContactFields.hpp" -#include "HybridContactsModuleSpec.hpp" -#include "StringHolder.hpp" -#include -#include -#include -#include -#include - -// C++ helpers for Swift -#include "ContactsModule-Swift-Cxx-Bridge.hpp" - -// Common C++ types used in Swift -#include -#include -#include -#include - -// Forward declarations of Swift defined types -// Forward declaration of `HybridContactsModuleSpecCxx` to properly resolve imports. -namespace ContactsModule { class HybridContactsModuleSpecCxx; } - -// Include Swift defined types -#if __has_include("ContactsModule-Swift.h") -// This header is generated by Xcode/Swift on every app build. -// If it cannot be found, make sure the Swift module's name (= podspec name) is actually "ContactsModule". -#include "ContactsModule-Swift.h" -// Same as above, but used when building with frameworks (`use_frameworks`) -#elif __has_include() -#include -#else -#error ContactsModule's autogenerated Swift header cannot be found! Make sure the Swift module's name (= podspec name) is actually "ContactsModule", and try building the app first. -#endif diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.mm b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.mm deleted file mode 100644 index e769fb6a6806..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.mm +++ /dev/null @@ -1,33 +0,0 @@ -/// -/// ContactsModuleAutolinking.mm -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#import -#import -#import "ContactsModule-Swift-Cxx-Umbrella.hpp" -#import - -#include "HybridContactsModuleSpecSwift.hpp" - -@interface ContactsModuleAutolinking : NSObject -@end - -@implementation ContactsModuleAutolinking - -+ (void) load { - using namespace margelo::nitro; - using namespace margelo::nitro::contacts; - - HybridObjectRegistry::registerHybridObjectConstructor( - "ContactsModule", - []() -> std::shared_ptr { - std::shared_ptr hybridObject = ContactsModule::ContactsModuleAutolinking::createContactsModule(); - return hybridObject; - } - ); -} - -@end diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.swift deleted file mode 100644 index 15d9d9b9064e..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.swift +++ /dev/null @@ -1,26 +0,0 @@ -/// -/// ContactsModuleAutolinking.swift -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -public final class ContactsModuleAutolinking { - public typealias bridge = margelo.nitro.contacts.bridge.swift - - /** - * Creates an instance of a Swift class that implements `HybridContactsModuleSpec`, - * and wraps it in a Swift class that can directly interop with C++ (`HybridContactsModuleSpecCxx`) - * - * This is generated by Nitrogen and will initialize the class specified - * in the `"autolinking"` property of `nitro.json` (in this case, `HybridContactsModule`). - */ - public static func createContactsModule() -> bridge.std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ { - let hybridObject = HybridContactsModule() - return { () -> bridge.std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ in - let __cxxWrapped = HybridContactsModuleSpecCxx(hybridObject) - let __pointer = HybridContactsModuleSpecCxxUnsafe.toUnsafe(__cxxWrapped) - return bridge.create_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(__pointer) - }() - } -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.cpp b/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.cpp deleted file mode 100644 index 71151f3c1883..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.cpp +++ /dev/null @@ -1,11 +0,0 @@ -/// -/// HybridContactsModuleSpecSwift.cpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#include "HybridContactsModuleSpecSwift.hpp" - -namespace margelo::nitro::contacts { -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.hpp b/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.hpp deleted file mode 100644 index dbb4fe829dc2..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/// -/// HybridContactsModuleSpecSwift.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -#include "HybridContactsModuleSpec.hpp" - -// Forward declaration of `HybridContactsModuleSpecCxx` to properly resolve imports. -namespace ContactsModule { class HybridContactsModuleSpecCxx; } - -// Forward declaration of `Contact` to properly resolve imports. -namespace margelo::nitro::contacts { struct Contact; } -// Forward declaration of `StringHolder` to properly resolve imports. -namespace margelo::nitro::contacts { struct StringHolder; } -// Forward declaration of `ContactFields` to properly resolve imports. -namespace margelo::nitro::contacts { enum class ContactFields; } - -#include -#include -#include "Contact.hpp" -#include -#include -#include "StringHolder.hpp" -#include "ContactFields.hpp" - -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif - -#include "ContactsModule-Swift-Cxx-Umbrella.hpp" - -namespace margelo::nitro::contacts { - - /** - * The C++ part of HybridContactsModuleSpecCxx.swift. - * - * HybridContactsModuleSpecSwift (C++) accesses HybridContactsModuleSpecCxx (Swift), and might - * contain some additional bridging code for C++ <> Swift interop. - * - * Since this obviously introduces an overhead, I hope at some point in - * the future, HybridContactsModuleSpecCxx can directly inherit from the C++ class HybridContactsModuleSpec - * to simplify the whole structure and memory management. - */ - class HybridContactsModuleSpecSwift: public virtual HybridContactsModuleSpec { - public: - // Constructor from a Swift instance - explicit HybridContactsModuleSpecSwift(const ContactsModule::HybridContactsModuleSpecCxx& swiftPart): - HybridObject(HybridContactsModuleSpec::TAG), - _swiftPart(swiftPart) { } - - public: - // Get the Swift part - inline ContactsModule::HybridContactsModuleSpecCxx getSwiftPart() noexcept { return _swiftPart; } - - public: - // Get memory pressure - inline size_t getExternalMemorySize() noexcept override { - return _swiftPart.getMemorySize(); - } - - public: - // Properties - - - public: - // Methods - inline std::shared_ptr>> getAll(const std::vector& keys) override { - auto __result = _swiftPart.getAll(keys); - return __result; - } - - private: - ContactsModule::HybridContactsModuleSpecCxx _swiftPart; - }; - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/Contact.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/Contact.swift deleted file mode 100644 index 404d6ba86b25..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/Contact.swift +++ /dev/null @@ -1,251 +0,0 @@ -/// -/// Contact.swift -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -import NitroModules - -/** - * Represents an instance of `Contact`, backed by a C++ struct. - */ -public typealias Contact = margelo.nitro.contacts.Contact - -public extension Contact { - private typealias bridge = margelo.nitro.contacts.bridge.swift - - /** - * Create a new instance of `Contact`. - */ - init(firstName: String?, lastName: String?, middleName: String?, phoneNumbers: [StringHolder]?, emailAddresses: [StringHolder]?, imageData: String?, thumbnailImageData: String?) { - self.init({ () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = firstName { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }(), { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = lastName { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }(), { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = middleName { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }(), { () -> bridge.std__optional_std__vector_StringHolder__ in - if let __unwrappedValue = phoneNumbers { - return bridge.create_std__optional_std__vector_StringHolder__({ () -> bridge.std__vector_StringHolder_ in - var __vector = bridge.create_std__vector_StringHolder_(__unwrappedValue.count) - for __item in __unwrappedValue { - __vector.push_back(__item) - } - return __vector - }()) - } else { - return .init() - } - }(), { () -> bridge.std__optional_std__vector_StringHolder__ in - if let __unwrappedValue = emailAddresses { - return bridge.create_std__optional_std__vector_StringHolder__({ () -> bridge.std__vector_StringHolder_ in - var __vector = bridge.create_std__vector_StringHolder_(__unwrappedValue.count) - for __item in __unwrappedValue { - __vector.push_back(__item) - } - return __vector - }()) - } else { - return .init() - } - }(), { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = imageData { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }(), { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = thumbnailImageData { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }()) - } - - var firstName: String? { - @inline(__always) - get { - return { () -> String? in - if let __unwrapped = self.__firstName.value { - return String(__unwrapped) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__firstName = { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }() - } - } - - var lastName: String? { - @inline(__always) - get { - return { () -> String? in - if let __unwrapped = self.__lastName.value { - return String(__unwrapped) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__lastName = { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }() - } - } - - var middleName: String? { - @inline(__always) - get { - return { () -> String? in - if let __unwrapped = self.__middleName.value { - return String(__unwrapped) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__middleName = { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }() - } - } - - var phoneNumbers: [StringHolder]? { - @inline(__always) - get { - return { () -> [StringHolder]? in - if let __unwrapped = self.__phoneNumbers.value { - return __unwrapped.map({ __item in __item }) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__phoneNumbers = { () -> bridge.std__optional_std__vector_StringHolder__ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__vector_StringHolder__({ () -> bridge.std__vector_StringHolder_ in - var __vector = bridge.create_std__vector_StringHolder_(__unwrappedValue.count) - for __item in __unwrappedValue { - __vector.push_back(__item) - } - return __vector - }()) - } else { - return .init() - } - }() - } - } - - var emailAddresses: [StringHolder]? { - @inline(__always) - get { - return { () -> [StringHolder]? in - if let __unwrapped = self.__emailAddresses.value { - return __unwrapped.map({ __item in __item }) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__emailAddresses = { () -> bridge.std__optional_std__vector_StringHolder__ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__vector_StringHolder__({ () -> bridge.std__vector_StringHolder_ in - var __vector = bridge.create_std__vector_StringHolder_(__unwrappedValue.count) - for __item in __unwrappedValue { - __vector.push_back(__item) - } - return __vector - }()) - } else { - return .init() - } - }() - } - } - - var imageData: String? { - @inline(__always) - get { - return { () -> String? in - if let __unwrapped = self.__imageData.value { - return String(__unwrapped) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__imageData = { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }() - } - } - - var thumbnailImageData: String? { - @inline(__always) - get { - return { () -> String? in - if let __unwrapped = self.__thumbnailImageData.value { - return String(__unwrapped) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__thumbnailImageData = { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }() - } - } -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/ContactFields.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/ContactFields.swift deleted file mode 100644 index ce38940795d9..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/ContactFields.swift +++ /dev/null @@ -1,64 +0,0 @@ -/// -/// ContactFields.swift -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -/** - * Represents the JS union `ContactFields`, backed by a C++ enum. - */ -public typealias ContactFields = margelo.nitro.contacts.ContactFields - -public extension ContactFields { - /** - * Get a ContactFields for the given String value, or - * return `nil` if the given value was invalid/unknown. - */ - init?(fromString string: String) { - switch string { - case "FIRST_NAME": - self = .firstName - case "LAST_NAME": - self = .lastName - case "MIDDLE_NAME": - self = .middleName - case "PHONE_NUMBERS": - self = .phoneNumbers - case "EMAIL_ADDRESSES": - self = .emailAddresses - case "IMAGE_DATA": - self = .imageData - case "THUMBNAIL_IMAGE_DATA": - self = .thumbnailImageData - case "GIVEN_NAME_KEY": - self = .givenNameKey - default: - return nil - } - } - - /** - * Get the String value this ContactFields represents. - */ - var stringValue: String { - switch self { - case .firstName: - return "FIRST_NAME" - case .lastName: - return "LAST_NAME" - case .middleName: - return "MIDDLE_NAME" - case .phoneNumbers: - return "PHONE_NUMBERS" - case .emailAddresses: - return "EMAIL_ADDRESSES" - case .imageData: - return "IMAGE_DATA" - case .thumbnailImageData: - return "THUMBNAIL_IMAGE_DATA" - case .givenNameKey: - return "GIVEN_NAME_KEY" - } - } -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpec.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpec.swift deleted file mode 100644 index 611110efca1d..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpec.swift +++ /dev/null @@ -1,36 +0,0 @@ -/// -/// HybridContactsModuleSpec.swift -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -import Foundation -import NitroModules - -/** - * A Swift protocol representing the ContactsModule HybridObject. - * Implement this protocol to create Swift-based instances of ContactsModule. - * - * When implementing this protocol, make sure to initialize `hybridContext` - example: - * ``` - * public class HybridContactsModule : HybridContactsModuleSpec { - * // Initialize HybridContext - * var hybridContext = margelo.nitro.HybridContext() - * - * // Return size of the instance to inform JS GC about memory pressure - * var memorySize: Int { - * return getSizeOf(self) - * } - * - * // ... - * } - * ``` - */ -public protocol HybridContactsModuleSpec: AnyObject, HybridObjectSpec { - // Properties - - - // Methods - func getAll(keys: [ContactFields]) throws -> Promise<[Contact]> -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpecCxx.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpecCxx.swift deleted file mode 100644 index 156cdf86bd7f..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpecCxx.swift +++ /dev/null @@ -1,123 +0,0 @@ -/// -/// HybridContactsModuleSpecCxx.swift -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -import Foundation -import NitroModules - -/** - * Helper class for converting instances of `HybridContactsModuleSpecCxx` from- and to unsafe pointers. - * This is useful to pass Swift classes to C++, without having to strongly type the C++ function signature. - * The actual Swift type can be included in the .cpp file, without having to forward-declare anything in .hpp. - */ -public final class HybridContactsModuleSpecCxxUnsafe { - /** - * Casts a `HybridContactsModuleSpecCxx` instance to a retained unsafe raw pointer. - * This acquires one additional strong reference on the object! - */ - public static func toUnsafe(_ instance: HybridContactsModuleSpecCxx) -> UnsafeMutableRawPointer { - return Unmanaged.passRetained(instance).toOpaque() - } - - /** - * Casts an unsafe pointer to a `HybridContactsModuleSpecCxx`. - * The pointer has to be a retained opaque `Unmanaged`. - * This removes one strong reference from the object! - */ - public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> HybridContactsModuleSpecCxx { - return Unmanaged.fromOpaque(pointer).takeRetainedValue() - } -} - -/** - * A class implementation that bridges HybridContactsModuleSpec over to C++. - * In C++, we cannot use Swift protocols - so we need to wrap it in a class to make it strongly defined. - * - * Also, some Swift types need to be bridged with special handling: - * - Enums need to be wrapped in Structs, otherwise they cannot be accessed bi-directionally (Swift bug: https://github.com/swiftlang/swift/issues/75330) - * - Other HybridObjects need to be wrapped/unwrapped from the Swift TCxx wrapper - * - Throwing methods need to be wrapped with a Result type, as exceptions cannot be propagated to C++ - */ -public class HybridContactsModuleSpecCxx { - /** - * The Swift <> C++ bridge's namespace (`margelo::nitro::contacts::bridge::swift`) - * from `ContactsModule-Swift-Cxx-Bridge.hpp`. - * This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift. - */ - public typealias bridge = margelo.nitro.contacts.bridge.swift - - /** - * Holds an instance of the `HybridContactsModuleSpec` Swift protocol. - */ - private var __implementation: any HybridContactsModuleSpec - - /** - * Create a new `HybridContactsModuleSpecCxx` that wraps the given `HybridContactsModuleSpec`. - * All properties and methods bridge to C++ types. - */ - public init(_ implementation: some HybridContactsModuleSpec) { - self.__implementation = implementation - /* no base class */ - } - - /** - * Get the actual `HybridContactsModuleSpec` instance this class wraps. - */ - @inline(__always) - public func getHybridContactsModuleSpec() -> any HybridContactsModuleSpec { - return __implementation - } - - /** - * Contains a (weak) reference to the C++ HybridObject to cache it. - */ - public var hybridContext: margelo.nitro.HybridContext { - @inline(__always) - get { - return self.__implementation.hybridContext - } - @inline(__always) - set { - self.__implementation.hybridContext = newValue - } - } - - /** - * Get the memory size of the Swift class (plus size of any other allocations) - * so the JS VM can properly track it and garbage-collect the JS object if needed. - */ - @inline(__always) - public var memorySize: Int { - return self.__implementation.memorySize - } - - // Properties - - - // Methods - @inline(__always) - public func getAll(keys: bridge.std__vector_ContactFields_) -> bridge.std__shared_ptr_Promise_std__vector_Contact___ { - do { - let __result = try self.__implementation.getAll(keys: keys.map({ __item in __item })) - return { () -> bridge.std__shared_ptr_Promise_std__vector_Contact___ in - let __promise = bridge.create_std__shared_ptr_Promise_std__vector_Contact___() - __result - .then({ __result in __promise.pointee.resolve({ () -> bridge.std__vector_Contact_ in - var __vector = bridge.create_std__vector_Contact_(__result.count) - for __item in __result { - __vector.push_back(__item) - } - return __vector - }()) }) - .catch({ __error in __promise.pointee.reject(__error.toCpp()) }) - return __promise - }() - } catch { - let __message = "\(error.localizedDescription)" - fatalError("Swift errors can currently not be propagated to C++! See https://github.com/swiftlang/swift/issues/75290 (Error: \(__message))") - } - } -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/StringHolder.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/StringHolder.swift deleted file mode 100644 index 477279082456..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/StringHolder.swift +++ /dev/null @@ -1,35 +0,0 @@ -/// -/// StringHolder.swift -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -import NitroModules - -/** - * Represents an instance of `StringHolder`, backed by a C++ struct. - */ -public typealias StringHolder = margelo.nitro.contacts.StringHolder - -public extension StringHolder { - private typealias bridge = margelo.nitro.contacts.bridge.swift - - /** - * Create a new instance of `StringHolder`. - */ - init(value: String) { - self.init(std.string(value)) - } - - var value: String { - @inline(__always) - get { - return String(self.__value) - } - @inline(__always) - set { - self.__value = std.string(newValue) - } - } -} diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/Contact.hpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/Contact.hpp deleted file mode 100644 index 6e4a5bd0c27e..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/Contact.hpp +++ /dev/null @@ -1,96 +0,0 @@ -/// -/// Contact.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif - -// Forward declaration of `StringHolder` to properly resolve imports. -namespace margelo::nitro::contacts { struct StringHolder; } - -#include -#include -#include -#include "StringHolder.hpp" - -namespace margelo::nitro::contacts { - - /** - * A struct which can be represented as a JavaScript object (Contact). - */ - struct Contact { - public: - std::optional firstName SWIFT_PRIVATE; - std::optional lastName SWIFT_PRIVATE; - std::optional middleName SWIFT_PRIVATE; - std::optional> phoneNumbers SWIFT_PRIVATE; - std::optional> emailAddresses SWIFT_PRIVATE; - std::optional imageData SWIFT_PRIVATE; - std::optional thumbnailImageData SWIFT_PRIVATE; - - public: - explicit Contact(std::optional firstName, std::optional lastName, std::optional middleName, std::optional> phoneNumbers, std::optional> emailAddresses, std::optional imageData, std::optional thumbnailImageData): firstName(firstName), lastName(lastName), middleName(middleName), phoneNumbers(phoneNumbers), emailAddresses(emailAddresses), imageData(imageData), thumbnailImageData(thumbnailImageData) {} - }; - -} // namespace margelo::nitro::contacts - -namespace margelo::nitro { - - using namespace margelo::nitro::contacts; - - // C++ Contact <> JS Contact (object) - template <> - struct JSIConverter { - static inline Contact fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { - jsi::Object obj = arg.asObject(runtime); - return Contact( - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "firstName")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "lastName")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "middleName")), - JSIConverter>>::fromJSI(runtime, obj.getProperty(runtime, "phoneNumbers")), - JSIConverter>>::fromJSI(runtime, obj.getProperty(runtime, "emailAddresses")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "imageData")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "thumbnailImageData")) - ); - } - static inline jsi::Value toJSI(jsi::Runtime& runtime, const Contact& arg) { - jsi::Object obj(runtime); - obj.setProperty(runtime, "firstName", JSIConverter>::toJSI(runtime, arg.firstName)); - obj.setProperty(runtime, "lastName", JSIConverter>::toJSI(runtime, arg.lastName)); - obj.setProperty(runtime, "middleName", JSIConverter>::toJSI(runtime, arg.middleName)); - obj.setProperty(runtime, "phoneNumbers", JSIConverter>>::toJSI(runtime, arg.phoneNumbers)); - obj.setProperty(runtime, "emailAddresses", JSIConverter>>::toJSI(runtime, arg.emailAddresses)); - obj.setProperty(runtime, "imageData", JSIConverter>::toJSI(runtime, arg.imageData)); - obj.setProperty(runtime, "thumbnailImageData", JSIConverter>::toJSI(runtime, arg.thumbnailImageData)); - return obj; - } - static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { - if (!value.isObject()) { - return false; - } - jsi::Object obj = value.getObject(runtime); - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "firstName"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "lastName"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "middleName"))) return false; - if (!JSIConverter>>::canConvert(runtime, obj.getProperty(runtime, "phoneNumbers"))) return false; - if (!JSIConverter>>::canConvert(runtime, obj.getProperty(runtime, "emailAddresses"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "imageData"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "thumbnailImageData"))) return false; - return true; - } - }; - -} // namespace margelo::nitro diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/ContactFields.hpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/ContactFields.hpp deleted file mode 100644 index c3e8c115465e..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/ContactFields.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/// -/// ContactFields.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif - -namespace margelo::nitro::contacts { - - /** - * An enum which can be represented as a JavaScript union (ContactFields). - */ - enum class ContactFields { - FIRST_NAME SWIFT_NAME(firstName) = 0, - LAST_NAME SWIFT_NAME(lastName) = 1, - MIDDLE_NAME SWIFT_NAME(middleName) = 2, - PHONE_NUMBERS SWIFT_NAME(phoneNumbers) = 3, - EMAIL_ADDRESSES SWIFT_NAME(emailAddresses) = 4, - IMAGE_DATA SWIFT_NAME(imageData) = 5, - THUMBNAIL_IMAGE_DATA SWIFT_NAME(thumbnailImageData) = 6, - GIVEN_NAME_KEY SWIFT_NAME(givenNameKey) = 7, - } CLOSED_ENUM; - -} // namespace margelo::nitro::contacts - -namespace margelo::nitro { - - using namespace margelo::nitro::contacts; - - // C++ ContactFields <> JS ContactFields (union) - template <> - struct JSIConverter { - static inline ContactFields fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { - std::string unionValue = JSIConverter::fromJSI(runtime, arg); - switch (hashString(unionValue.c_str(), unionValue.size())) { - case hashString("FIRST_NAME"): return ContactFields::FIRST_NAME; - case hashString("LAST_NAME"): return ContactFields::LAST_NAME; - case hashString("MIDDLE_NAME"): return ContactFields::MIDDLE_NAME; - case hashString("PHONE_NUMBERS"): return ContactFields::PHONE_NUMBERS; - case hashString("EMAIL_ADDRESSES"): return ContactFields::EMAIL_ADDRESSES; - case hashString("IMAGE_DATA"): return ContactFields::IMAGE_DATA; - case hashString("THUMBNAIL_IMAGE_DATA"): return ContactFields::THUMBNAIL_IMAGE_DATA; - case hashString("GIVEN_NAME_KEY"): return ContactFields::GIVEN_NAME_KEY; - default: [[unlikely]] - throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum ContactFields - invalid value!"); - } - } - static inline jsi::Value toJSI(jsi::Runtime& runtime, ContactFields arg) { - switch (arg) { - case ContactFields::FIRST_NAME: return JSIConverter::toJSI(runtime, "FIRST_NAME"); - case ContactFields::LAST_NAME: return JSIConverter::toJSI(runtime, "LAST_NAME"); - case ContactFields::MIDDLE_NAME: return JSIConverter::toJSI(runtime, "MIDDLE_NAME"); - case ContactFields::PHONE_NUMBERS: return JSIConverter::toJSI(runtime, "PHONE_NUMBERS"); - case ContactFields::EMAIL_ADDRESSES: return JSIConverter::toJSI(runtime, "EMAIL_ADDRESSES"); - case ContactFields::IMAGE_DATA: return JSIConverter::toJSI(runtime, "IMAGE_DATA"); - case ContactFields::THUMBNAIL_IMAGE_DATA: return JSIConverter::toJSI(runtime, "THUMBNAIL_IMAGE_DATA"); - case ContactFields::GIVEN_NAME_KEY: return JSIConverter::toJSI(runtime, "GIVEN_NAME_KEY"); - default: [[unlikely]] - throw std::invalid_argument("Cannot convert ContactFields to JS - invalid value: " - + std::to_string(static_cast(arg)) + "!"); - } - } - static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { - if (!value.isString()) { - return false; - } - std::string unionValue = JSIConverter::fromJSI(runtime, value); - switch (hashString(unionValue.c_str(), unionValue.size())) { - case hashString("FIRST_NAME"): - case hashString("LAST_NAME"): - case hashString("MIDDLE_NAME"): - case hashString("PHONE_NUMBERS"): - case hashString("EMAIL_ADDRESSES"): - case hashString("IMAGE_DATA"): - case hashString("THUMBNAIL_IMAGE_DATA"): - case hashString("GIVEN_NAME_KEY"): - return true; - default: - return false; - } - } - }; - -} // namespace margelo::nitro diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.cpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.cpp deleted file mode 100644 index eba17de8d910..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/// -/// HybridContactsModuleSpec.cpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#include "HybridContactsModuleSpec.hpp" - -namespace margelo::nitro::contacts { - - void HybridContactsModuleSpec::loadHybridMethods() { - // load base methods/properties - HybridObject::loadHybridMethods(); - // load custom methods/properties - registerHybrids(this, [](Prototype& prototype) { - prototype.registerHybridMethod("getAll", &HybridContactsModuleSpec::getAll); - }); - } - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.hpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.hpp deleted file mode 100644 index 6c298086f493..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/// -/// HybridContactsModuleSpec.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif - -// Forward declaration of `Contact` to properly resolve imports. -namespace margelo::nitro::contacts { struct Contact; } -// Forward declaration of `ContactFields` to properly resolve imports. -namespace margelo::nitro::contacts { enum class ContactFields; } - -#include -#include -#include "Contact.hpp" -#include "ContactFields.hpp" - -namespace margelo::nitro::contacts { - - using namespace margelo::nitro; - - /** - * An abstract base class for `ContactsModule` - * Inherit this class to create instances of `HybridContactsModuleSpec` in C++. - * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. - * @example - * ```cpp - * class HybridContactsModule: public HybridContactsModuleSpec { - * public: - * HybridContactsModule(...): HybridObject(TAG) { ... } - * // ... - * }; - * ``` - */ - class HybridContactsModuleSpec: public virtual HybridObject { - public: - // Constructor - explicit HybridContactsModuleSpec(): HybridObject(TAG) { } - - // Destructor - virtual ~HybridContactsModuleSpec() { } - - public: - // Properties - - - public: - // Methods - virtual std::shared_ptr>> getAll(const std::vector& keys) = 0; - - protected: - // Hybrid Setup - void loadHybridMethods() override; - - protected: - // Tag for logging - static constexpr auto TAG = "ContactsModule"; - }; - -} // namespace margelo::nitro::contacts diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/StringHolder.hpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/StringHolder.hpp deleted file mode 100644 index 1a666ed1faca..000000000000 --- a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/StringHolder.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/// -/// StringHolder.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2024 Marc Rousavy @ Margelo -/// - -#pragma once - -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif - - - -#include - -namespace margelo::nitro::contacts { - - /** - * A struct which can be represented as a JavaScript object (StringHolder). - */ - struct StringHolder { - public: - std::string value SWIFT_PRIVATE; - - public: - explicit StringHolder(std::string value): value(value) {} - }; - -} // namespace margelo::nitro::contacts - -namespace margelo::nitro { - - using namespace margelo::nitro::contacts; - - // C++ StringHolder <> JS StringHolder (object) - template <> - struct JSIConverter { - static inline StringHolder fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { - jsi::Object obj = arg.asObject(runtime); - return StringHolder( - JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "value")) - ); - } - static inline jsi::Value toJSI(jsi::Runtime& runtime, const StringHolder& arg) { - jsi::Object obj(runtime); - obj.setProperty(runtime, "value", JSIConverter::toJSI(runtime, arg.value)); - return obj; - } - static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { - if (!value.isObject()) { - return false; - } - jsi::Object obj = value.getObject(runtime); - if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "value"))) return false; - return true; - } - }; - -} // namespace margelo::nitro diff --git a/modules/ContactsNitroModule/package.json b/modules/ContactsNitroModule/package.json deleted file mode 100644 index 6f70882a2193..000000000000 --- a/modules/ContactsNitroModule/package.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "name": "contacts-nitro-module", - "version": "0.0.1", - "main": "src/index", - "react-native": "src/index", - "description": "React Native Contacts Module with Nitro optimization", - "source": "src/index", - "files": [ - "src", - "react-native.config.js", - "lib", - "android/build.gradle", - "android/gradle.properties", - "android/CMakeLists.txt", - "android/src", - "ios/**/*.h", - "ios/**/*.m", - "ios/**/*.mm", - "ios/**/*.cpp", - "ios/**/*.swift", - "app.plugin.js", - "*.podspec", - "README.md" - ], - "scripts": { - "postinstall": "tsc || exit 0;", - "typecheck": "tsc --noEmit", - "clean": "del-cli android/build node_modules/**/android/build lib", - "lint": "eslint \"**/*.{js,ts,tsx}\" --fix", - "lint-ci": "eslint \"**/*.{js,ts,tsx}\" -f @jamesacarr/github-actions", - "typescript": "tsc --noEmit false", - "specs-debug": "bun run --filter=\"**\" typescript && bun nitro-codegen --logLevel=\"debug\"", - "specs": "bun nitro-codegen" - }, - "keywords": [ - "react-native", - "nitro" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/mrousavy/nitro.git" - }, - "author": "Marc Rousavy (https://github.com/mrousavy)", - "license": "MIT", - "bugs": { - "url": "https://github.com/mrousavy/nitro/issues" - }, - "homepage": "https://github.com/mrousavy/nitro#readme", - "publishConfig": { - "registry": "https://registry.npmjs.org/" - }, - "devDependencies": { - "@react-native/eslint-config": "^0.75.2", - "@types/jest": "^29.5.12", - "@types/react": "^18.3.4", - "del-cli": "^5.1.0", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", - "nitro-codegen": "0.18.1", - "prettier": "^3.3.3", - "react": "^18.3.1", - "react-native": "0.75.2", - "react-native-nitro-modules": "*", - "typescript": "^5.5.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - }, - "eslintConfig": { - "root": true, - "extends": [ - "@react-native", - "prettier" - ], - "plugins": ["prettier"], - "rules": { - "prettier/prettier": [ - "warn", - { - "quoteProps": "consistent", - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "es5", - "useTabs": false - } - ] - } - }, - "eslintIgnore": [ - "node_modules/", - "lib/" - ], - "prettier": { - "quoteProps": "consistent", - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "es5", - "useTabs": false, - "semi": false - } -} diff --git a/modules/ContactsNitroModule/src/ContactsModule.nitro.ts b/modules/ContactsNitroModule/src/ContactsModule.nitro.ts deleted file mode 100644 index 8df839f550e8..000000000000 --- a/modules/ContactsNitroModule/src/ContactsModule.nitro.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { HybridObject } from 'react-native-nitro-modules' - -interface StringHolder { - value: string -} - -export interface Contact { - firstName?: string - lastName?: string - middleName?: string - phoneNumbers?: StringHolder[] - emailAddresses?: StringHolder[] - imageData?: string - thumbnailImageData?: string -} -export type ContactFields = - | 'FIRST_NAME' - | 'LAST_NAME' - | 'MIDDLE_NAME' - | 'PHONE_NUMBERS' - | 'EMAIL_ADDRESSES' - | 'IMAGE_DATA' - | 'THUMBNAIL_IMAGE_DATA' - | 'GIVEN_NAME_KEY' - -export interface ContactsModule - extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { - getAll(keys: ContactFields[]): Promise -} diff --git a/modules/ContactsNitroModule/src/index.ts b/modules/ContactsNitroModule/src/index.ts deleted file mode 100644 index acc7c7c1fb76..000000000000 --- a/modules/ContactsNitroModule/src/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ContactsModule } from './ContactsModule.nitro' -import type { Contact } from './ContactsModule.nitro' -import { NitroModules } from 'react-native-nitro-modules' - -export const ContactsNitroModule = - NitroModules.createHybridObject('ContactsModule') - -export type { Contact } diff --git a/modules/ContactsNitroModule/tsconfig.json b/modules/ContactsNitroModule/tsconfig.json deleted file mode 100644 index e30dc47ac169..000000000000 --- a/modules/ContactsNitroModule/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "include": ["src"], - "compilerOptions": { - "composite": true, - "outDir": "lib", - "rootDir": "src", - "allowUnreachableCode": false, - "allowUnusedLabels": false, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "jsx": "react", - "lib": ["esnext"], - "module": "esnext", - "moduleResolution": "node", - "noEmit": false, - "noFallthroughCasesInSwitch": true, - "noImplicitReturns": true, - "noImplicitUseStrict": false, - "noStrictGenericChecks": false, - "noUncheckedIndexedAccess": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true, - "target": "esnext", - "verbatimModuleSyntax": true - } -} diff --git a/package-lock.json b/package-lock.json index 0a2e67f38ee2..59a6a4c895c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,17 @@ { "name": "new.expensify", - "version": "9.0.77-4", + "version": "9.0.79-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.77-4", + "version": "9.0.79-3", "hasInstallScript": true, "license": "MIT", - "workspaces": [ - "modules/ContactsNitroModule" - ], "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.209", + "@expensify/react-native-live-markdown": "0.1.210", "@expo/metro-runtime": "~3.2.3", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -49,7 +46,6 @@ "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", - "contacts-nitro-module": "./modules/ContactsNitroModule", "core-js": "^3.32.0", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", @@ -99,7 +95,6 @@ "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-nitro-modules": "^0.18.1", "react-native-onyx": "2.0.86", "react-native-pager-view": "6.5.1", "react-native-pdf": "6.7.3", @@ -280,114 +275,10 @@ "webpack-dev-server": "^5.0.4", "webpack-merge": "^5.8.0", "xlsx": "file:vendor/xlsx-0.20.3.tgz" - } - }, - "modules/ContactsNitroModule": { - "name": "contacts-nitro-module", - "version": "0.0.1", - "hasInstallScript": true, - "license": "MIT", - "devDependencies": { - "@react-native/eslint-config": "^0.75.2", - "@types/jest": "^29.5.12", - "@types/react": "^18.3.4", - "del-cli": "^5.1.0", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", - "nitro-codegen": "0.18.1", - "prettier": "^3.3.3", - "react": "^18.3.1", - "react-native": "0.75.2", - "react-native-nitro-modules": "*", - "typescript": "^5.5.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "modules/ContactsNitroModule/node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "modules/ContactsNitroModule/node_modules/@types/react": { - "version": "18.3.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.17.tgz", - "integrity": "sha512-opAQ5no6LqJNo9TqnxBKsgnkIYHozW9KSTlFVoSUJYh1Fl/sswkEoqIugRSm7tbh6pABtYjGAjW+GOS23j8qbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "modules/ContactsNitroModule/node_modules/eslint-plugin-prettier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", - "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.9.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": "*", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, - "modules/ContactsNitroModule/node_modules/prettier": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", - "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "modules/ContactsNitroModule/node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" + "node": "20.18.0", + "npm": "10.8.2" } }, "node_modules/@actions/core": { @@ -3607,9 +3498,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.209", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.209.tgz", - "integrity": "sha512-u+RRY+Jog/llEu9T1v0okSLgRhG5jGlX9H1Je0A8HWv0439XFLnAWSvN2eQ2T7bvT8Yjdj5CcC0hkgJiB9oCQw==", + "version": "0.1.210", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.210.tgz", + "integrity": "sha512-CW9DY2yN/QJrqkD6+74s+kWQ9bhWQwd2jT+x5RCgyy5N2SdcoE8G8DGQQvmo6q94KcRkHIr/HsTVOyzACQ/nrw==", "hasInstallScript": true, "license": "MIT", "workspaces": [ @@ -7308,19 +7199,6 @@ "node": ">=14" } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", @@ -9431,83 +9309,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native/eslint-config": { - "version": "0.75.4", - "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.75.4.tgz", - "integrity": "sha512-3KBHYwp4HnBdaCFx9KDPvQY+sGrv5fHX2qDkXGKmN3uYBz+zfnMQXTiht6OuBbWULUF0y0o8m+uH1yYAn/V9mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/eslint-parser": "^7.20.0", - "@react-native/eslint-plugin": "0.75.4", - "@typescript-eslint/eslint-plugin": "^7.1.1", - "@typescript-eslint/parser": "^7.1.1", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-ft-flow": "^2.0.1", - "eslint-plugin-jest": "^27.9.0", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-native": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": ">=8", - "prettier": ">=2" - } - }, - "node_modules/@react-native/eslint-config/node_modules/eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/@react-native/eslint-config/node_modules/eslint-plugin-jest": { - "version": "27.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", - "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^5.10.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", - "eslint": "^7.0.0 || ^8.0.0", - "jest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/@react-native/eslint-plugin": { - "version": "0.75.4", - "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.75.4.tgz", - "integrity": "sha512-1kEZzC8UKi3baHnH7tBVCNpF4aoAmT7g7hEa5/rtZ+Z7vcpaxeY6wjNYt3j02Z9n310yX0NKDJox30CqvzEvsg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@react-native/gradle-plugin": { "version": "0.75.2", "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.2.tgz", @@ -13304,44 +13105,6 @@ "node": ">=10.13.0" } }, - "node_modules/@ts-morph/common": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.25.0.tgz", - "integrity": "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimatch": "^9.0.4", - "path-browserify": "^1.0.1", - "tinyglobby": "^0.2.9" - } - }, - "node_modules/@ts-morph/common/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "dev": true, @@ -13862,13 +13625,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ms": { "version": "0.7.34", "dev": true, @@ -13910,13 +13666,6 @@ "@types/node": "*" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/parse-json": { "version": "4.0.0", "dev": true, @@ -15749,16 +15498,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/asap": { "version": "2.0.6", "license": "MIT" @@ -17553,38 +17292,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/camelcase-keys": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", - "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.3.0", - "map-obj": "^4.1.0", - "quick-lru": "^5.1.1", - "type-fest": "^1.2.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/camelize": { "version": "1.0.1", "license": "MIT", @@ -17948,13 +17655,6 @@ "node": ">= 0.12.0" } }, - "node_modules/code-block-writer": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", - "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", - "dev": true, - "license": "MIT" - }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "dev": true, @@ -18446,10 +18146,6 @@ "dev": true, "license": "MIT" }, - "node_modules/contacts-nitro-module": { - "resolved": "modules/ContactsNitroModule", - "link": true - }, "node_modules/content-disposition": { "version": "0.5.4", "dev": true, @@ -19166,56 +18862,6 @@ } } }, - "node_modules/decamelize": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", - "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js": { "version": "10.4.3", "dev": true, @@ -19408,184 +19054,6 @@ "node": ">=6" } }, - "node_modules/del-cli": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-5.1.0.tgz", - "integrity": "sha512-xwMeh2acluWeccsfzE7VLsG3yTr7nWikbfw+xhMnpRrF15pGSkw+3/vJZWlGoE4I86UiLRNHicmKt4tkIX9Jtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "del": "^7.1.0", - "meow": "^10.1.3" - }, - "bin": { - "del": "cli.js", - "del-cli": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/aggregate-error": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", - "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^4.0.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/clean-stack": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", - "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/del": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-7.1.0.tgz", - "integrity": "sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "globby": "^13.1.2", - "graceful-fs": "^4.2.10", - "is-glob": "^4.0.3", - "is-path-cwd": "^3.0.0", - "is-path-inside": "^4.0.0", - "p-map": "^5.5.0", - "rimraf": "^3.0.2", - "slash": "^4.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/is-path-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz", - "integrity": "sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/p-map": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", - "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del-cli/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/del/node_modules/array-union": { "version": "1.0.2", "dev": true, @@ -23928,16 +23396,6 @@ "node": ">=0.10.0" } }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "dev": true, @@ -25336,16 +24794,6 @@ "node": ">=6" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-plain-object": { "version": "5.0.0", "dev": true, @@ -28979,19 +28427,6 @@ "tmpl": "1.0.5" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/map-or-similar": { "version": "1.5.0", "dev": true, @@ -29198,92 +28633,6 @@ "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", "integrity": "sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==" }, - "node_modules/meow": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", - "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.2", - "camelcase-keys": "^7.0.0", - "decamelize": "^5.0.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.2", - "read-pkg-up": "^8.0.0", - "redent": "^4.0.0", - "trim-newlines": "^4.0.2", - "type-fest": "^1.2.2", - "yargs-parser": "^20.2.9" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/redent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", - "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^5.0.0", - "strip-indent": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/strip-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", - "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -29879,21 +29228,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/minipass": { "version": "3.3.6", "license": "ISC", @@ -30133,37 +29467,6 @@ "version": "1.0.5", "license": "MIT" }, - "node_modules/nitro-codegen": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/nitro-codegen/-/nitro-codegen-0.18.1.tgz", - "integrity": "sha512-gDOHIIFFY89Ibo/Q8Dlzx4Rk9fCaGnby4Er5Dh1xV4J5hMqTfqo2VjG+RxScdUTYy/SKOc0UsB2faQybs5+GDw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "react-native-nitro-modules": "^0.18.1", - "ts-morph": "^24.0.0", - "yargs": "^17.7.2", - "zod": "^3.23.8" - }, - "bin": { - "nitro-codegen": "lib/index.js" - } - }, - "node_modules/nitro-codegen/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/no-case": { "version": "3.0.4", "dev": true, @@ -30397,22 +29700,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "license": "MIT", @@ -32869,17 +32156,6 @@ "react-native": ">=0.65.0" } }, - "node_modules/react-native-nitro-modules": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/react-native-nitro-modules/-/react-native-nitro-modules-0.18.2.tgz", - "integrity": "sha512-eHsq1cRfm/Bz1Nq7KctTqxAqhzVSNo0WGX281xARZh+vOq8633Qxn1NHRZ5/Rno2Bla6HOXlUW6RoW0wKM/7kg==", - "hasInstallScript": true, - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, "node_modules/react-native-onyx": { "version": "2.0.86", "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.86.tgz", @@ -33681,69 +32957,6 @@ "node": ">=6" } }, - "node_modules/read-pkg": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", - "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", - "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^5.0.0", - "read-pkg": "^6.0.0", - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "license": "MIT", @@ -35143,17 +34356,6 @@ "version": "0.0.2", "dev": true }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/spdx-exceptions": { "version": "2.3.0", "dev": true, @@ -35866,23 +35068,6 @@ "dev": true, "license": "MIT" }, - "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/tabbable": { "version": "6.2.0", "license": "MIT" @@ -36316,48 +35501,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tinyglobby": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", - "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", - "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyqueue": { "version": "2.0.3", "license": "ISC" @@ -36473,19 +35616,6 @@ "tree-kill": "cli.js" } }, - "node_modules/trim-newlines": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", - "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/trim-right": { "version": "1.0.1", "license": "MIT", @@ -36590,15 +35720,12 @@ "node": ">=10" } }, - "node_modules/ts-morph": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-24.0.0.tgz", - "integrity": "sha512-2OAOg/Ob5yx9Et7ZX4CvTCc0UFoZHwLEJ+dpDPSUi5TgwwlTlX47w+iFRrEwzUZwYACjq83cgjS/Da50Ga37uw==", + "node_modules/ts-jest/node_modules/yargs-parser": { + "version": "21.1.1", "dev": true, - "license": "MIT", - "dependencies": { - "@ts-morph/common": "~0.25.0", - "code-block-writer": "^13.0.3" + "license": "ISC", + "engines": { + "node": ">=12" } }, "node_modules/ts-node": { @@ -37374,17 +36501,6 @@ "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/validate-npm-package-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", @@ -38377,20 +37493,18 @@ "node": ">=12" } }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/yargs/node_modules/y18n": { + "version": "5.0.8", "license": "ISC", "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/yargs/node_modules/y18n": { - "version": "5.0.8", + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", "license": "ISC", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yauzl": { diff --git a/package.json b/package.json index c621b583452e..790ea5582da8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.77-4", + "version": "9.0.79-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -44,7 +44,7 @@ "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "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": "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\" \":!modules/**\")", + "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 .", @@ -76,7 +76,7 @@ }, "dependencies": { "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.209", + "@expensify/react-native-live-markdown": "0.1.210", "@expo/metro-runtime": "~3.2.3", "@firebase/app": "^0.10.10", "@firebase/performance": "^0.6.8", @@ -111,7 +111,6 @@ "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", - "contacts-nitro-module": "./modules/ContactsNitroModule", "core-js": "^3.32.0", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", @@ -162,7 +161,6 @@ "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", "react-native-onyx": "2.0.86", - "react-native-nitro-modules": "^0.18.1", "react-native-pager-view": "6.5.1", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -378,7 +376,17 @@ ] } }, - "workspaces": [ - "modules/ContactsNitroModule" - ] + "electronmon": { + "patterns": [ + "!src/**", + "!ios/**", + "!android/**", + "!tests/**", + "*.test.*" + ] + }, + "engines": { + "node": "20.18.0", + "npm": "10.8.2" + } } diff --git a/react-native.config.js b/react-native.config.js index ffbc8f5c65cb..773375378acd 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -1,5 +1,3 @@ -const path = require('path'); -const pak = require('./modules/ContactsNitroModule/package.json'); const iosSourceDir = process.env.PROJECT_ROOT_PATH ? process.env.PROJECT_ROOT_PATH + 'ios' : 'ios'; const androidSourceDir = process.env.PROJECT_ROOT_PATH ? process.env.PROJECT_ROOT_PATH + 'android' : 'android'; @@ -9,9 +7,4 @@ module.exports = { android: {sourceDir: androidSourceDir}, }, assets: ['./assets/fonts/native'], - dependencies: { - [pak.name]: { - root: path.join(__dirname, 'modules', 'ContactsNitroModule'), - }, - }, }; diff --git a/scripts/applyPatches.sh b/scripts/applyPatches.sh index 9ba8360ea39f..fa87b4540b38 100755 --- a/scripts/applyPatches.sh +++ b/scripts/applyPatches.sh @@ -16,10 +16,13 @@ function patchPackage { OS="$(uname)" if [[ "$OS" == "Darwin" || "$OS" == "Linux" ]]; then npx patch-package --error-on-fail --color=always + EXIT_CODE=$? if [[ "$IS_HYBRID_APP_REPO" == "true" && "$NEW_DOT_FLAG" == "false" ]]; then echo -e "\n${GREEN}Applying HybridApp patches!${NC}" npx patch-package --patch-dir 'Mobile-Expensify/patches' --error-on-fail --color=always + EXIT_CODE+=$? fi + exit $EXIT_CODE else error "Unsupported OS: $OS" exit 1 diff --git a/src/App.tsx b/src/App.tsx index 6a4eac7ec7da..38fcd5c156d5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,7 @@ import KeyboardProvider from './components/KeyboardProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; +import {ProductTrainingContextProvider} from './components/ProductTrainingContext'; import SafeArea from './components/SafeArea'; import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider'; import {SearchRouterContextProvider} from './components/Search/SearchRouter/SearchRouterContext'; @@ -91,6 +92,7 @@ function App({url}: AppProps) { VideoPopoverMenuContextProvider, KeyboardProvider, SearchRouterContextProvider, + ProductTrainingContextProvider, ]} > diff --git a/src/CONST.ts b/src/CONST.ts index 1e1516fd2c8b..1c1cd1df1892 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1152,6 +1152,7 @@ const CONST = { UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE', LEAVE_POLICY: 'POLICYCHANGELOG_LEAVE_POLICY', CORPORATE_UPGRADE: 'POLICYCHANGELOG_CORPORATE_UPGRADE', + TEAM_DOWNGRADE: 'POLICYCHANGELOG_TEAM_DOWNGRADE', }, ROOM_CHANGE_LOG: { INVITE_TO_ROOM: 'INVITETOROOM', @@ -1335,6 +1336,9 @@ const CONST = { SEARCH_OPTION_LIST_DEBOUNCE_TIME: 300, RESIZE_DEBOUNCE_TIME: 100, UNREAD_UPDATE_DEBOUNCE_TIME: 300, + SEARCH_CONVERT_SEARCH_VALUES: 'search_convert_search_values', + SEARCH_MAKE_TREE: 'search_make_tree', + SEARCH_BUILD_TREE: 'search_build_tree', SEARCH_FILTER_OPTIONS: 'search_filter_options', USE_DEBOUNCED_STATE_DELAY: 300, LIST_SCROLLING_DEBOUNCE_TIME: 200, @@ -6437,19 +6441,22 @@ const CONST = { }, }, - DEVICE_CONTACT: { - FIRST_NAME: 'FIRST_NAME', - LAST_NAME: 'LAST_NAME', - PHONE_NUMBERS: 'PHONE_NUMBERS', - EMAIL_ADDRESSES: 'EMAIL_ADDRESSES', - IMAGE_DATA: 'IMAGE_DATA', - }, - HYBRID_APP: { REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront', }, MIGRATED_USER_WELCOME_MODAL: 'migratedUserWelcomeModal', + + PRODUCT_TRAINING_TOOLTIP_NAMES: { + CONCEIRGE_LHN_GBR: 'conciergeLHNGBR', + RENAME_SAVED_SEARCH: 'renameSavedSearch', + QUICK_ACTION_BUTTON: 'quickActionButton', + WORKSAPCE_CHAT_CREATE: 'workspaceChatCreate', + SEARCH_FILTER_BUTTON_TOOLTIP: 'filterButtonTooltip', + BOTTOM_NAV_INBOX_TOOLTIP: 'bottomNavInboxTooltip', + LHN_WORKSPACE_CHAT_TOOLTIP: 'workspaceChatLHNTooltip', + GLOBAL_CREATE_TOOLTIP: 'globalCreateTooltip', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a43f1622ec9a..026ab2310622 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -117,9 +117,6 @@ const ONYXKEYS = { /** NVP keys */ - /** Boolean flag only true when first set */ - NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER: 'nvp_isFirstTimeNewExpensifyUser', - /** This NVP contains list of at most 5 recent attendees */ NVP_RECENT_ATTENDEES: 'nvp_expensify_recentAttendees', @@ -222,18 +219,9 @@ const ONYXKEYS = { /** The end date (epoch timestamp) of the workspace owner’s grace period after the free trial ends. */ NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END: 'nvp_private_billingGracePeriodEnd', - /** The NVP containing all information related to educational tooltip in workspace chat */ - NVP_WORKSPACE_TOOLTIP: 'workspaceTooltip', - /** The NVP containing the target url to navigate to when deleting a transaction */ NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL: 'nvp_deleteTransactionNavigateBackURL', - /** Whether to show save search rename tooltip */ - SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP: 'shouldShowSavedSearchRenameTooltip', - - /** Whether to hide gbr tooltip */ - NVP_SHOULD_HIDE_GBR_TOOLTIP: 'nvp_should_hide_gbr_tooltip', - /** Does this user have push notifications enabled for this device? */ PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', @@ -888,10 +876,8 @@ type OnyxCollectionValuesMapping = { type OnyxValuesMapping = { [ONYXKEYS.ACCOUNT]: OnyxTypes.Account; [ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID]: string; - [ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER]: boolean; - // NVP_ONBOARDING is an array for old users. - [ONYXKEYS.NVP_ONBOARDING]: Onboarding | []; + [ONYXKEYS.NVP_ONBOARDING]: Onboarding; // ONYXKEYS.NVP_TRYNEWDOT is HybridApp onboarding data [ONYXKEYS.NVP_TRYNEWDOT]: OnyxTypes.TryNewDot; @@ -1031,9 +1017,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_BILLING_FUND_ID]: number; [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: number; [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: number; - [ONYXKEYS.NVP_WORKSPACE_TOOLTIP]: OnyxTypes.WorkspaceTooltip; [ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL]: string | undefined; - [ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP]: boolean; [ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS]: OnyxTypes.CancellationDetails[]; [ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE]: string; [ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx; @@ -1041,7 +1025,6 @@ type OnyxValuesMapping = { [ONYXKEYS.LAST_ROUTE]: string; [ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: boolean | undefined; [ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean; - [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; [ONYXKEYS.CONCIERGE_REPORT_ID]: string; [ONYXKEYS.PRESERVED_USER_SESSION]: OnyxTypes.Session; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 58d28a46a7b8..909f847fd75d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -702,11 +702,11 @@ const ROUTES = { }, WORKSPACE_INVITE: { route: 'settings/workspaces/:policyID/invite', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/invite` as const, + getRoute: (policyID: string, backTo?: string) => `${getUrlWithBackToParam(`settings/workspaces/${policyID}/invite`, backTo)}` as const, }, WORKSPACE_INVITE_MESSAGE: { route: 'settings/workspaces/:policyID/invite-message', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/invite-message` as const, + getRoute: (policyID: string, backTo?: string) => `${getUrlWithBackToParam(`settings/workspaces/${policyID}/invite-message`, backTo)}` as const, }, WORKSPACE_PROFILE: { route: 'settings/workspaces/:policyID/profile', diff --git a/src/components/AmountTextInput.tsx b/src/components/AmountTextInput.tsx index 6be2b43c09d7..12189d22dba0 100644 --- a/src/components/AmountTextInput.tsx +++ b/src/components/AmountTextInput.tsx @@ -39,7 +39,7 @@ type AmountTextInputProps = { /** Hide the focus styles on TextInput */ hideFocusedState?: boolean; -} & Pick; +} & Pick; function AmountTextInput( { diff --git a/src/components/Composer/implementation/index.native.tsx b/src/components/Composer/implementation/index.native.tsx index 0cddb32f5aeb..a02767d24c87 100644 --- a/src/components/Composer/implementation/index.native.tsx +++ b/src/components/Composer/implementation/index.native.tsx @@ -1,7 +1,7 @@ import type {MarkdownStyle} from '@expensify/react-native-live-markdown'; import mimeDb from 'mime-db'; import type {ForwardedRef} from 'react'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {NativeSyntheticEvent, TextInput, TextInputChangeEventData, TextInputPasteEventData} from 'react-native'; import {StyleSheet} from 'react-native'; import type {FileObject} from '@components/AttachmentModal'; @@ -9,6 +9,7 @@ import type {ComposerProps} from '@components/Composer/types'; import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput'; import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useKeyboardState from '@hooks/useKeyboardState'; import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useResetComposerFocus from '@hooks/useResetComposerFocus'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -37,6 +38,7 @@ function Composer( selection, value, isGroupPolicyReport = false, + showSoftInputOnFocus = true, ...props }: ComposerProps, ref: ForwardedRef, @@ -49,7 +51,11 @@ function Composer( const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const [contextMenuHidden, setContextMenuHidden] = useState(true); + const {inputCallbackRef, inputRef: autoFocusInputRef} = useAutoFocusInput(); + const keyboardState = useKeyboardState(); + const isKeyboardShown = keyboardState?.isKeyboardShown ?? false; useEffect(() => { if (autoFocus === !!autoFocusInputRef.current) { @@ -58,6 +64,13 @@ function Composer( inputCallbackRef(autoFocus ? textInput.current : null); }, [autoFocus, inputCallbackRef, autoFocusInputRef]); + useEffect(() => { + if (!showSoftInputOnFocus || !isKeyboardShown) { + return; + } + setContextMenuHidden(false); + }, [showSoftInputOnFocus, isKeyboardShown]); + useEffect(() => { if (!textInput.current || !textInput.current.setSelection || !selection || isComposerFullSize) { return; @@ -158,6 +171,8 @@ function Composer( props?.onBlur?.(e); }} onClear={onClear} + showSoftInputOnFocus={showSoftInputOnFocus} + contextMenuHidden={contextMenuHidden} /> ); } diff --git a/src/components/Composer/implementation/index.tsx b/src/components/Composer/implementation/index.tsx index 5af76a2406b5..9171132964f6 100755 --- a/src/components/Composer/implementation/index.tsx +++ b/src/components/Composer/implementation/index.tsx @@ -5,7 +5,7 @@ import type {BaseSyntheticEvent, ForwardedRef} from 'react'; import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; // eslint-disable-next-line no-restricted-imports import type {NativeSyntheticEvent, TextInput, TextInputKeyPressEventData, TextInputSelectionChangeEventData} from 'react-native'; -import {DeviceEventEmitter, StyleSheet} from 'react-native'; +import {DeviceEventEmitter, InteractionManager, StyleSheet} from 'react-native'; import type {ComposerProps} from '@components/Composer/types'; import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput'; import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; @@ -50,6 +50,7 @@ function Composer( isComposerFullSize = false, shouldContainScroll = true, isGroupPolicyReport = false, + showSoftInputOnFocus = true, ...props }: ComposerProps, ref: ForwardedRef, @@ -74,6 +75,11 @@ function Composer( }); const [hasMultipleLines, setHasMultipleLines] = useState(false); const [isRendered, setIsRendered] = useState(false); + + // On mobile safari, the cursor will move from right to left with inputMode set to none during report transition + // To avoid that we should hide the cursor util the transition is finished + const [shouldTransparentCursor, setShouldTransparentCursor] = useState(!showSoftInputOnFocus && Browser.isMobileSafari()); + const isScrollBarVisible = useIsScrollBarVisible(textInput, value ?? ''); const [prevScroll, setPrevScroll] = useState(); const [prevHeight, setPrevHeight] = useState(); @@ -260,6 +266,15 @@ function Composer( setIsRendered(true); }, []); + useEffect(() => { + if (!shouldTransparentCursor) { + return; + } + InteractionManager.runAfterInteractions(() => { + setShouldTransparentCursor(false); + }); + }, [shouldTransparentCursor]); + const clear = useCallback(() => { if (!textInput.current) { return; @@ -347,11 +362,12 @@ function Composer( placeholderTextColor={theme.placeholderText} ref={(el) => (textInput.current = el)} selection={selection} - style={[inputStyleMemo]} + style={[inputStyleMemo, shouldTransparentCursor ? {caretColor: 'transparent'} : undefined]} markdownStyle={markdownStyle} value={value} defaultValue={defaultValue} autoFocus={autoFocus} + inputMode={showSoftInputOnFocus ? 'text' : 'none'} /* eslint-disable-next-line react/jsx-props-no-spreading */ {...props} onSelectionChange={addCursorPositionToSelectionChange} diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts index 3df5508f1dd7..6ea3bdb2f824 100644 --- a/src/components/Composer/types.ts +++ b/src/components/Composer/types.ts @@ -68,6 +68,9 @@ type ComposerProps = Omit & { /** Indicates whether the composer is in a group policy report. Used for disabling report mentioning style in markdown input */ isGroupPolicyReport?: boolean; + + /** Whether to show the keyboard on focus */ + showSoftInputOnFocus?: boolean; }; export type {TextSelection, ComposerProps, CustomSelectionChangeEvent}; diff --git a/src/components/ConnectToNetSuiteFlow/index.tsx b/src/components/ConnectToNetSuiteFlow/index.tsx index 1d33eb07df4f..7957896d4006 100644 --- a/src/components/ConnectToNetSuiteFlow/index.tsx +++ b/src/components/ConnectToNetSuiteFlow/index.tsx @@ -59,10 +59,11 @@ function ConnectToNetSuiteFlow({policyID}: ConnectToNetSuiteFlowProps) { if (threeDotsMenuContainerRef) { if (!shouldUseNarrowLayout) { threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { - setReuseConnectionPopoverPosition({ - horizontal: x + width, - vertical: y + height, - }); + const horizontal = x + width; + const vertical = y + height; + if (reuseConnectionPopoverPosition.horizontal !== horizontal || reuseConnectionPopoverPosition.vertical !== vertical) { + setReuseConnectionPopoverPosition({horizontal, vertical}); + } }); } diff --git a/src/components/ConnectToSageIntacctFlow/index.tsx b/src/components/ConnectToSageIntacctFlow/index.tsx index f93fce9c668a..807082365042 100644 --- a/src/components/ConnectToSageIntacctFlow/index.tsx +++ b/src/components/ConnectToSageIntacctFlow/index.tsx @@ -64,10 +64,11 @@ function ConnectToSageIntacctFlow({policyID}: ConnectToSageIntacctFlowProps) { if (threeDotsMenuContainerRef) { if (!shouldUseNarrowLayout) { threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { - setReuseConnectionPopoverPosition({ - horizontal: x + width, - vertical: y + height, - }); + const horizontal = x + width; + const vertical = y + height; + if (reuseConnectionPopoverPosition.horizontal !== horizontal || reuseConnectionPopoverPosition.vertical !== vertical) { + setReuseConnectionPopoverPosition({horizontal, vertical}); + } }); } diff --git a/src/components/ContactPermissionModal/index.native.tsx b/src/components/ContactPermissionModal/index.native.tsx deleted file mode 100644 index 825c8bc4afbe..000000000000 --- a/src/components/ContactPermissionModal/index.native.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, {useEffect, useState} from 'react'; -import {InteractionManager} from 'react-native'; -import {RESULTS} from 'react-native-permissions'; -import ConfirmModal from '@components/ConfirmModal'; -import * as Illustrations from '@components/Icon/Illustrations'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import {getContactPermission} from '@libs/ContactPermission'; -import type {ContactPermissionModalProps} from './types'; - -function ContactPermissionModal({startPermissionFlow, resetPermissionFlow, onDeny, onGrant}: ContactPermissionModalProps) { - const [isModalVisible, setIsModalVisible] = useState(false); - - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - useEffect(() => { - if (!startPermissionFlow) { - return; - } - getContactPermission().then((status) => { - if (status === RESULTS.GRANTED || status === RESULTS.LIMITED) { - return onGrant(); - } - if (status === RESULTS.BLOCKED) { - return; - } - setIsModalVisible(true); - }); - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- We only want to run this effect when startPermissionFlow changes - }, [startPermissionFlow]); - - const handleGrantPermission = () => { - setIsModalVisible(false); - InteractionManager.runAfterInteractions(onGrant); - }; - - const handleDenyPermission = () => { - onDeny(RESULTS.DENIED); - setIsModalVisible(false); - }; - - const handleCloseModal = () => { - setIsModalVisible(false); - resetPermissionFlow(); - }; - - return ( - - ); -} - -ContactPermissionModal.displayName = 'ContactPermissionModal'; - -export default ContactPermissionModal; diff --git a/src/components/ContactPermissionModal/index.tsx b/src/components/ContactPermissionModal/index.tsx deleted file mode 100644 index 3f7e25bac590..000000000000 --- a/src/components/ContactPermissionModal/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import type {ContactPermissionModalProps} from './types'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function ContactPermissionModal(props: ContactPermissionModalProps) { - return null; -} - -ContactPermissionModal.displayName = 'ContactPermissionModal'; - -export default ContactPermissionModal; diff --git a/src/components/ContactPermissionModal/types.ts b/src/components/ContactPermissionModal/types.ts deleted file mode 100644 index 5c831410656f..000000000000 --- a/src/components/ContactPermissionModal/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type {PermissionStatus} from 'react-native-permissions'; - -type ContactPermissionModalProps = { - /** A callback to call when the permission has been granted */ - onGrant: () => void; - - /** A callback to call when the permission has been denied */ - onDeny: (permission: PermissionStatus) => void; - - /** Should start the permission flow? */ - startPermissionFlow: boolean; - - /** Reset the permission flow */ - resetPermissionFlow: () => void; -}; - -export default {}; - -export type {ContactPermissionModalProps}; diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index 67a9a2fc83f3..313b5d620f42 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -3,8 +3,10 @@ import {View} from 'react-native'; import type {TupleToUnion} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; import BlockingView from './BlockingViews/BlockingView'; import * as Illustrations from './Icon/Illustrations'; import ScrollView from './ScrollView'; @@ -34,13 +36,19 @@ function EmptySelectionListContent({contentType}: EmptySelectionListContentProps const EmptySubtitle = ( {translate(`emptyList.${contentType}.subtitleText1`)} - {translate(`emptyList.${contentType}.subtitleText2`)} + { + Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SUBMIT_EXPENSE, Navigation.getActiveRouteWithoutParams())); + }} + > + {translate(`emptyList.${contentType}.subtitleText2`)} + {translate(`emptyList.${contentType}.subtitleText3`)} ); return ( - + (null); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const platform = getPlatform(); + const isNarrowScreenOnWeb = shouldUseNarrowLayout && platform === CONST.PLATFORM.WEB; + const isFocused = useIsFocused(); + const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED, {initialValue: false}); + const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.GLOBAL_CREATE_TOOLTIP, + isFocused && isSidebarLoaded, + ); const sharedValue = useSharedValue(isActive ? 1 : 0); const buttonRef = ref; @@ -97,32 +114,45 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo }; return ( - { - fabPressable.current = el ?? null; - if (buttonRef && 'current' in buttonRef) { - buttonRef.current = el ?? null; - } + {}} - role={role} - shouldUseHapticsOnLongPress={false} + shouldUseOverlay + shiftHorizontal={isNarrowScreenOnWeb ? 0 : variables.fabTooltipShiftHorizontal} + renderTooltipContent={renderProductTrainingTooltip} + wrapperStyle={styles.productTrainingTooltipWrapper} + onHideTooltip={hideProductTrainingTooltip} > - - - - - - + { + fabPressable.current = el ?? null; + if (buttonRef && 'current' in buttonRef) { + buttonRef.current = el ?? null; + } + }} + style={[styles.h100, styles.bottomTabBarItem]} + accessibilityLabel={accessibilityLabel} + onPress={toggleFabAction} + onLongPress={() => {}} + role={role} + shouldUseHapticsOnLongPress={false} + > + + + + + + + ); } diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index b4d097e90994..363fe238e9f4 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -182,7 +182,7 @@ function HeaderWithBackButton({ width={iconWidth ?? variables.iconHeader} height={iconHeight ?? variables.iconHeader} additionalStyles={[styles.mr2, iconStyles]} - fill={iconFill ?? theme.icon} + fill={iconFill} /> )} {!!policyAvatar && ( diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 4093b44743fe..02a6843dc11f 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -32,6 +32,7 @@ import Box from '@assets/images/box.svg'; import Briefcase from '@assets/images/briefcase.svg'; import Bug from '@assets/images/bug.svg'; import Building from '@assets/images/building.svg'; +import Buildings from '@assets/images/buildings.svg'; import CalendarSolid from '@assets/images/calendar-solid.svg'; import Calendar from '@assets/images/calendar.svg'; import Camera from '@assets/images/camera.svg'; @@ -235,6 +236,7 @@ export { Briefcase, Bug, Building, + Buildings, Calendar, Camera, Car, diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 4379142619ff..0debd4585e7b 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -74,6 +74,8 @@ import BankArrow from '@assets/images/simple-illustrations/simple-illustration__ import BigRocket from '@assets/images/simple-illustrations/simple-illustration__bigrocket.svg'; import PinkBill from '@assets/images/simple-illustrations/simple-illustration__bill.svg'; import Binoculars from '@assets/images/simple-illustrations/simple-illustration__binoculars.svg'; +import Building from '@assets/images/simple-illustrations/simple-illustration__building.svg'; +import Buildings from '@assets/images/simple-illustrations/simple-illustration__buildings.svg'; import CarIce from '@assets/images/simple-illustrations/simple-illustration__car-ice.svg'; import Car from '@assets/images/simple-illustrations/simple-illustration__car.svg'; import ChatBubbles from '@assets/images/simple-illustrations/simple-illustration__chatbubbles.svg'; @@ -228,6 +230,8 @@ export { PendingBank, ThreeLeggedLaptopWoman, House, + Building, + Buildings, Alert, TeachersUnite, Abacus, diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index c423d3101d92..ead50d44634c 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,5 +1,5 @@ import {useFocusEffect} from '@react-navigation/native'; -import React, {useCallback, useRef, useState} from 'react'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; import type {GestureResponderEvent, ViewStyle} from 'react-native'; import {StyleSheet, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -11,6 +11,7 @@ import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {useSession} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; @@ -22,7 +23,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; -import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; @@ -32,7 +32,6 @@ import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportA import FreeTrial from '@pages/settings/Subscription/FreeTrial'; import variables from '@styles/variables'; import Timing from '@userActions/Timing'; -import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -48,18 +47,21 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); - const [isFirstTimeNewExpensifyUser] = useOnyx(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER); - const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { - selector: hasCompletedGuidedSetupFlowSelector, - }); + const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const session = useSession(); - - // Guides are assigned for the MANAGE_TEAM onboarding action, except for emails that have a '+'. + const isActiveWorkspaceChat = ReportUtils.isPolicyExpenseChat(report) && activePolicyID === report?.policyID && session?.accountID === report?.ownerAccountID; const isOnboardingGuideAssigned = introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+'); - const shouldShowToooltipOnThisReport = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); - const [shouldHideGBRTooltip] = useOnyx(ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP, {initialValue: true}); + const shouldShowGetStartedTooltip = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); + + const {tooltipToRender, shouldShowTooltip} = useMemo(() => { + const tooltip = shouldShowGetStartedTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.LHN_WORKSPACE_CHAT_TOOLTIP; + + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + return {tooltipToRender: tooltip, shouldShowTooltip: shouldUseNarrowLayout ? isScreenFocused : true}; + }, [shouldShowGetStartedTooltip, isScreenFocused, shouldUseNarrowLayout]); + const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender, shouldShowTooltip); const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); @@ -72,30 +74,6 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti }, []), ); - const renderGBRTooltip = useCallback( - () => ( - - - {translate('sidebarScreen.tooltip')} - - ), - [ - styles.alignItemsCenter, - styles.flexRow, - styles.justifyContentCenter, - styles.flexWrap, - styles.textAlignCenter, - styles.gap1, - styles.quickActionTooltipSubtitle, - theme.tooltipHighlightText, - translate, - ], - ); - const isInFocusMode = viewMode === CONST.OPTION_MODE.COMPACT; const sidebarInnerRowStyle = StyleSheet.flatten( isInFocusMode @@ -180,17 +158,18 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti needsOffscreenAlphaCompositing > diff --git a/src/components/MagicCodeInput.tsx b/src/components/MagicCodeInput.tsx index 4100b877e2da..951471fdf76c 100644 --- a/src/components/MagicCodeInput.tsx +++ b/src/components/MagicCodeInput.tsx @@ -114,36 +114,44 @@ function MagicCodeInput( ) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const inputRefs = useRef(); + const inputRef = useRef(); const [input, setInput] = useState(TEXT_INPUT_EMPTY_STATE); const [focusedIndex, setFocusedIndex] = useState(0); - const [editIndex, setEditIndex] = useState(0); + const editIndex = useRef(0); const [wasSubmitted, setWasSubmitted] = useState(false); const shouldFocusLast = useRef(false); const inputWidth = useRef(0); const lastFocusedIndex = useRef(0); const lastValue = useRef(TEXT_INPUT_EMPTY_STATE); + const valueRef = useRef(value); useEffect(() => { lastValue.current = input.length; }, [input]); + useEffect(() => { + // Note: there are circumstances where the value state isn't updated yet + // when e.g. onChangeText gets called the next time. In those cases its safer to access the value from a ref + // to not have outdated values. + valueRef.current = value; + }, [value]); + const blurMagicCodeInput = () => { - inputRefs.current?.blur(); + inputRef.current?.blur(); setFocusedIndex(undefined); }; const focusMagicCodeInput = () => { setFocusedIndex(0); lastFocusedIndex.current = 0; - setEditIndex(0); - inputRefs.current?.focus(); + editIndex.current = 0; + inputRef.current?.focus(); }; const setInputAndIndex = (index: number) => { setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(index); - setEditIndex(index); + editIndex.current = index; }; useImperativeHandle(ref, () => ({ @@ -151,7 +159,7 @@ function MagicCodeInput( focusMagicCodeInput(); }, focusLastSelected() { - inputRefs.current?.focus(); + inputRef.current?.focus(); }, resetFocus() { setInput(TEXT_INPUT_EMPTY_STATE); @@ -160,7 +168,7 @@ function MagicCodeInput( clear() { lastFocusedIndex.current = 0; setInputAndIndex(0); - inputRefs.current?.focus(); + inputRef.current?.focus(); onChangeTextProp(''); }, blur() { @@ -219,7 +227,7 @@ function MagicCodeInput( // TapGestureHandler works differently on mobile web and native app // On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) { - inputRefs.current?.focus(); + inputRef.current?.focus(); } setInputAndIndex(index); lastFocusedIndex.current = index; @@ -231,6 +239,12 @@ function MagicCodeInput( * the focused input on the next empty one, if exists. * It handles both fast typing and only one digit at a time * in a specific position. + * + * Note: this works under the assumption that the backing text input will always have a cleared text, + * and entering text will exactly call onChangeText with one new character/digit. + * When the OS is inserting one time passwords for example it will call this method successively with one more digit each time. + * Thus, this method relies on an internal value ref to make sure to always use the latest value (as state updates are async, and + * might happen later than the next call to onChangeText). */ const onChangeText = (textValue?: string) => { if (!textValue?.length || !ValidationUtils.isNumeric(textValue)) { @@ -249,16 +263,17 @@ function MagicCodeInput( const numbersArr = addedValue .trim() .split('') - .slice(0, maxLength - editIndex); - const updatedFocusedIndex = Math.min(editIndex + (numbersArr.length - 1) + 1, maxLength - 1); + .slice(0, maxLength - editIndex.current); + const updatedFocusedIndex = Math.min(editIndex.current + (numbersArr.length - 1) + 1, maxLength - 1); - let numbers = decomposeString(value, maxLength); - numbers = [...numbers.slice(0, editIndex), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex, maxLength)]; + let numbers = decomposeString(valueRef.current, maxLength); + numbers = [...numbers.slice(0, editIndex.current), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex.current, maxLength)]; setInputAndIndex(updatedFocusedIndex); const finalInput = composeToString(numbers); onChangeTextProp(finalInput); + valueRef.current = finalInput; }; /** @@ -275,12 +290,13 @@ function MagicCodeInput( // If keyboard is disabled and no input is focused we need to remove // the last entered digit and focus on the correct input if (isDisableKeyboard && focusedIndex === undefined) { - const indexBeforeLastEditIndex = editIndex === 0 ? editIndex : editIndex - 1; + const curEditIndex = editIndex.current; + const indexBeforeLastEditIndex = curEditIndex === 0 ? curEditIndex : curEditIndex - 1; - const indexToFocus = numbers.at(editIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : editIndex; + const indexToFocus = numbers.at(curEditIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : curEditIndex; if (indexToFocus !== undefined) { lastFocusedIndex.current = indexToFocus; - inputRefs.current?.focus(); + inputRef.current?.focus(); } onChangeTextProp(value.substring(0, indexToFocus)); @@ -292,7 +308,7 @@ function MagicCodeInput( if (focusedIndex !== undefined && numbers?.at(focusedIndex) !== CONST.MAGIC_CODE_EMPTY_CHAR) { setInput(TEXT_INPUT_EMPTY_STATE); numbers = [...numbers.slice(0, focusedIndex), CONST.MAGIC_CODE_EMPTY_CHAR, ...numbers.slice(focusedIndex + 1, maxLength)]; - setEditIndex(focusedIndex); + editIndex.current = focusedIndex; onChangeTextProp(composeToString(numbers)); return; } @@ -318,17 +334,17 @@ function MagicCodeInput( if (newFocusedIndex !== undefined) { lastFocusedIndex.current = newFocusedIndex; - inputRefs.current?.focus(); + inputRef.current?.focus(); } } if (keyValue === 'ArrowLeft' && focusedIndex !== undefined) { const newFocusedIndex = Math.max(0, focusedIndex - 1); setInputAndIndex(newFocusedIndex); - inputRefs.current?.focus(); + inputRef.current?.focus(); } else if (keyValue === 'ArrowRight' && focusedIndex !== undefined) { const newFocusedIndex = Math.min(focusedIndex + 1, maxLength - 1); setInputAndIndex(newFocusedIndex); - inputRefs.current?.focus(); + inputRef.current?.focus(); } else if (keyValue === 'Enter') { // We should prevent users from submitting when it's offline. if (isOffline) { @@ -340,7 +356,7 @@ function MagicCodeInput( const newFocusedIndex = (event as unknown as KeyboardEvent).shiftKey ? focusedIndex - 1 : focusedIndex + 1; if (newFocusedIndex >= 0 && newFocusedIndex < maxLength) { setInputAndIndex(newFocusedIndex); - inputRefs.current?.focus(); + inputRef.current?.focus(); if (event?.preventDefault) { event.preventDefault(); } @@ -383,7 +399,7 @@ function MagicCodeInput( onLayout={(e) => { inputWidth.current = e.nativeEvent.layout.width; }} - ref={(inputRef) => (inputRefs.current = inputRef)} + ref={(newRef) => (inputRef.current = newRef)} autoFocus={autoFocus} inputMode="numeric" textContentType="oneTimeCode" diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 9ef33900bb00..717659c16fd3 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -12,6 +12,7 @@ import CONST from '@src/CONST'; import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused'; import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; +import type {TextInputWithCurrencySymbolProps} from './TextInputWithCurrencySymbol/types'; type CurrentMoney = {amount: string; currency: string}; @@ -91,7 +92,7 @@ type MoneyRequestAmountInputProps = { /** The width of inner content */ contentWidth?: number; -}; +} & Pick; type Selection = { start: number; @@ -126,6 +127,7 @@ function MoneyRequestAmountInput( hideFocusedState = true, shouldKeepUserInput = false, autoGrow = true, + autoGrowExtraSpace, contentWidth, ...props }: MoneyRequestAmountInputProps, @@ -289,6 +291,7 @@ function MoneyRequestAmountInput( return ( ; + +type ShouldShowConditionProps = { + shouldUseNarrowLayout?: boolean; +}; + +type TooltipData = { + content: Array<{text: TranslationPaths; isBold: boolean}>; + onHideTooltip: () => void; + name: ProductTrainingTooltipName; + priority: number; + shouldShow: (props: ShouldShowConditionProps) => boolean; +}; + +const TOOLTIPS: Record = { + [CONCEIRGE_LHN_GBR]: { + content: [ + {text: 'productTrainingTooltip.conciergeLHNGBR.part1', isBold: false}, + {text: 'productTrainingTooltip.conciergeLHNGBR.part2', isBold: true}, + ], + onHideTooltip: () => dismissProductTraining(CONCEIRGE_LHN_GBR), + name: CONCEIRGE_LHN_GBR, + priority: 1300, + shouldShow: ({shouldUseNarrowLayout}) => !!shouldUseNarrowLayout, + }, + [RENAME_SAVED_SEARCH]: { + content: [ + {text: 'productTrainingTooltip.saveSearchTooltip.part1', isBold: true}, + {text: 'productTrainingTooltip.saveSearchTooltip.part2', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(RENAME_SAVED_SEARCH), + name: RENAME_SAVED_SEARCH, + priority: 1250, + shouldShow: ({shouldUseNarrowLayout}) => !shouldUseNarrowLayout, + }, + [GLOBAL_CREATE_TOOLTIP]: { + content: [ + {text: 'productTrainingTooltip.globalCreateTooltip.part1', isBold: true}, + {text: 'productTrainingTooltip.globalCreateTooltip.part2', isBold: false}, + {text: 'productTrainingTooltip.globalCreateTooltip.part3', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(GLOBAL_CREATE_TOOLTIP), + name: GLOBAL_CREATE_TOOLTIP, + priority: 1200, + shouldShow: () => true, + }, + [QUICK_ACTION_BUTTON]: { + content: [ + {text: 'productTrainingTooltip.quickActionButton.part1', isBold: true}, + {text: 'productTrainingTooltip.quickActionButton.part2', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(QUICK_ACTION_BUTTON), + name: QUICK_ACTION_BUTTON, + priority: 1150, + shouldShow: () => true, + }, + [WORKSAPCE_CHAT_CREATE]: { + content: [ + {text: 'productTrainingTooltip.workspaceChatCreate.part1', isBold: true}, + {text: 'productTrainingTooltip.workspaceChatCreate.part2', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(WORKSAPCE_CHAT_CREATE), + name: WORKSAPCE_CHAT_CREATE, + priority: 1100, + shouldShow: () => true, + }, + [SEARCH_FILTER_BUTTON_TOOLTIP]: { + content: [ + {text: 'productTrainingTooltip.searchFilterButtonTooltip.part1', isBold: true}, + {text: 'productTrainingTooltip.searchFilterButtonTooltip.part2', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(SEARCH_FILTER_BUTTON_TOOLTIP), + name: SEARCH_FILTER_BUTTON_TOOLTIP, + priority: 1000, + shouldShow: () => true, + }, + [BOTTOM_NAV_INBOX_TOOLTIP]: { + content: [ + {text: 'productTrainingTooltip.bottomNavInboxTooltip.part1', isBold: true}, + {text: 'productTrainingTooltip.bottomNavInboxTooltip.part2', isBold: false}, + {text: 'productTrainingTooltip.bottomNavInboxTooltip.part3', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(BOTTOM_NAV_INBOX_TOOLTIP), + name: BOTTOM_NAV_INBOX_TOOLTIP, + priority: 900, + shouldShow: () => true, + }, + [LHN_WORKSPACE_CHAT_TOOLTIP]: { + content: [ + {text: 'productTrainingTooltip.workspaceChatTooltip.part1', isBold: true}, + {text: 'productTrainingTooltip.workspaceChatTooltip.part2', isBold: false}, + {text: 'productTrainingTooltip.workspaceChatTooltip.part3', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(LHN_WORKSPACE_CHAT_TOOLTIP), + name: LHN_WORKSPACE_CHAT_TOOLTIP, + priority: 800, + shouldShow: () => true, + }, +}; + +export default TOOLTIPS; +export type {ProductTrainingTooltipName}; diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx new file mode 100644 index 000000000000..5d964f5f5671 --- /dev/null +++ b/src/components/ProductTrainingContext/index.tsx @@ -0,0 +1,224 @@ +import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; +import type {ProductTrainingTooltipName} from './TOOLTIPS'; +import TOOLTIPS from './TOOLTIPS'; + +type ProductTrainingContextType = { + shouldRenderTooltip: (tooltipName: ProductTrainingTooltipName) => boolean; + registerTooltip: (tooltipName: ProductTrainingTooltipName) => void; + unregisterTooltip: (tooltipName: ProductTrainingTooltipName) => void; +}; + +const ProductTrainingContext = createContext({ + shouldRenderTooltip: () => false, + registerTooltip: () => {}, + unregisterTooltip: () => {}, +}); + +function ProductTrainingContextProvider({children}: ChildrenProps) { + const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT); + const hasBeenAddedToNudgeMigration = !!tryNewDot?.nudgeMigration?.timestamp; + const [isOnboardingCompleted = true, isOnboardingCompletedMetadata] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { + selector: hasCompletedGuidedSetupFlowSelector, + }); + const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + + const [activeTooltips, setActiveTooltips] = useState>(new Set()); + + const unregisterTooltip = useCallback( + (tooltipName: ProductTrainingTooltipName) => { + setActiveTooltips((prev) => { + const next = new Set(prev); + next.delete(tooltipName); + return next; + }); + }, + [setActiveTooltips], + ); + + const determineVisibleTooltip = useCallback(() => { + if (activeTooltips.size === 0) { + return null; + } + + const sortedTooltips = Array.from(activeTooltips) + .map((name) => ({ + name, + priority: TOOLTIPS[name]?.priority ?? 0, + })) + .sort((a, b) => b.priority - a.priority); + + const highestPriorityTooltip = sortedTooltips.at(0); + + if (!highestPriorityTooltip) { + return null; + } + + return highestPriorityTooltip.name; + }, [activeTooltips]); + + const shouldTooltipBeVisible = useCallback( + (tooltipName: ProductTrainingTooltipName) => { + if (isLoadingOnyxValue(isOnboardingCompletedMetadata)) { + return false; + } + + const isDismissed = !!dismissedProductTraining?.[tooltipName]; + + if (isDismissed) { + return false; + } + const tooltipConfig = TOOLTIPS[tooltipName]; + + // if hasBeenAddedToNudgeMigration is true, and welcome modal is not dismissed, don't show tooltip + if (hasBeenAddedToNudgeMigration && !dismissedProductTraining?.[CONST.MIGRATED_USER_WELCOME_MODAL]) { + return false; + } + if (isOnboardingCompleted === false) { + return false; + } + + return tooltipConfig.shouldShow({ + shouldUseNarrowLayout, + }); + }, + [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, isOnboardingCompletedMetadata, shouldUseNarrowLayout], + ); + + const registerTooltip = useCallback( + (tooltipName: ProductTrainingTooltipName) => { + const shouldRegister = shouldTooltipBeVisible(tooltipName); + if (!shouldRegister) { + return; + } + setActiveTooltips((prev) => new Set([...prev, tooltipName])); + }, + [shouldTooltipBeVisible], + ); + + const shouldRenderTooltip = useCallback( + (tooltipName: ProductTrainingTooltipName) => { + // First check base conditions + const shouldShow = shouldTooltipBeVisible(tooltipName); + if (!shouldShow) { + return false; + } + const visibleTooltip = determineVisibleTooltip(); + + // If this is the highest priority visible tooltip, show it + if (tooltipName === visibleTooltip) { + return true; + } + + return false; + }, + [shouldTooltipBeVisible, determineVisibleTooltip], + ); + + const contextValue = useMemo( + () => ({ + shouldRenderTooltip, + registerTooltip, + unregisterTooltip, + }), + [shouldRenderTooltip, registerTooltip, unregisterTooltip], + ); + + return {children}; +} + +const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shouldShow = true) => { + const context = useContext(ProductTrainingContext); + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate} = useLocalize(); + + if (!context) { + throw new Error('useProductTourContext must be used within a ProductTourProvider'); + } + + const {shouldRenderTooltip, registerTooltip, unregisterTooltip} = context; + + useEffect(() => { + if (shouldShow) { + registerTooltip(tooltipName); + return () => { + unregisterTooltip(tooltipName); + }; + } + return () => {}; + }, [tooltipName, registerTooltip, unregisterTooltip, shouldShow]); + + const renderProductTrainingTooltip = useCallback(() => { + const tooltip = TOOLTIPS[tooltipName]; + return ( + + + + {tooltip.content.map(({text, isBold}) => { + const translatedText = translate(text); + return ( + + {translatedText} + + ); + })} + + + ); + }, [ + styles.alignItemsCenter, + styles.flexRow, + styles.flexWrap, + styles.gap3, + styles.justifyContentCenter, + styles.mw100, + styles.p2, + styles.productTrainingTooltipText, + styles.textAlignCenter, + styles.textBold, + styles.textWrap, + theme.tooltipHighlightText, + tooltipName, + translate, + ]); + + const shouldShowProductTrainingTooltip = useMemo(() => { + return shouldShow && shouldRenderTooltip(tooltipName); + }, [shouldRenderTooltip, tooltipName, shouldShow]); + + const hideProductTrainingTooltip = useCallback(() => { + const tooltip = TOOLTIPS[tooltipName]; + tooltip.onHideTooltip(); + unregisterTooltip(tooltipName); + }, [tooltipName, unregisterTooltip]); + + return { + renderProductTrainingTooltip, + hideProductTrainingTooltip, + shouldShowProductTrainingTooltip, + }; +}; + +export {ProductTrainingContextProvider, useProductTrainingContext}; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index ba0cda25d59e..86196f13d662 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -74,20 +74,20 @@ function MoneyRequestPreviewContent({ const route = useRoute>(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || '-1'}`); + const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || CONST.DEFAULT_NUMBER_ID}`); const [session] = useOnyx(ONYXKEYS.SESSION); - const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID || '-1'}`); + const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID || CONST.DEFAULT_NUMBER_ID}`); const policy = PolicyUtils.getPolicy(iouReport?.policyID); const isMoneyRequestAction = ReportActionsUtils.isMoneyRequestAction(action); - const transactionID = isMoneyRequestAction ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : '-1'; + const transactionID = isMoneyRequestAction ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const sessionAccountID = session?.accountID; - const managerID = iouReport?.managerID ?? -1; - const ownerAccountID = iouReport?.ownerAccountID ?? -1; + const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID; + const ownerAccountID = iouReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID; const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); const participantAccountIDs = @@ -117,9 +117,9 @@ function MoneyRequestPreviewContent({ const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID ?? '-1', transactionViolations, true); + const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID, transactionViolations, true); + const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID, transactionViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID, transactionViolations, true); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); @@ -155,8 +155,8 @@ function MoneyRequestPreviewContent({ const shouldShowHoldMessage = !(isSettled && !isSettlementOrApprovalPartial) && !!transaction?.comment?.hold; const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`); - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); - const reviewingTransactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? '-1' : '-1'; + const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); + const reviewingTransactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; /* Show the merchant for IOUs and expenses only if: @@ -253,10 +253,10 @@ function MoneyRequestPreviewContent({ if (TransactionUtils.isPending(transaction)) { return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; } - if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID ?? '-1', iouReport, policy)) { + if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1', transactionViolations))) { + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID, transactionViolations))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; @@ -301,12 +301,8 @@ function MoneyRequestPreviewContent({ // Clear the draft before selecting a different expense to prevent merging fields from the previous expense // (e.g., category, tag, tax) that may be not enabled/available in the new expense's policy. Transaction.abandonReviewDuplicateTransactions(); - const comparisonResult = TransactionUtils.compareDuplicateTransactionFields( - reviewingTransactionID, - transaction?.reportID ?? '', - transaction?.transactionID ?? reviewingTransactionID, - ); - Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? '', reportID: transaction?.reportID}); + const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID, transaction?.transactionID ?? reviewingTransactionID); + Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID, reportID: transaction?.reportID}); if ('merchant' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID, backTo)); @@ -349,11 +345,13 @@ function MoneyRequestPreviewContent({ !onPreviewPressed ? [styles.moneyRequestPreviewBox, containerStyles] : {}, ]} > - + {!isDeleted && ( + + )} {isEmptyObject(transaction) && !ReportActionsUtils.isMessageDeleted(action) && action.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? ( ) : ( diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 79497e5fab88..a4ade8d77aa8 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -103,7 +103,7 @@ function ReportPreview({ const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); const [invoiceReceiverPolicy] = useOnyx( - `${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : -1}`, + `${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : CONST.DEFAULT_NUMBER_ID}`, ); const theme = useTheme(); const styles = useThemeStyles(); @@ -144,10 +144,10 @@ function ReportPreview({ const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport); const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = ReportUtils.getNonHeldAndFullAmount(iouReport, shouldShowPayButton); - const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(iouReport?.reportID ?? ''); - const hasHeldExpenses = ReportUtils.hasHeldExpenses(iouReport?.reportID ?? ''); + const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(iouReport?.reportID); + const hasHeldExpenses = ReportUtils.hasHeldExpenses(iouReport?.reportID); - const managerID = iouReport?.managerID ?? action.childManagerAccountID ?? 0; + const managerID = iouReport?.managerID ?? action.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID; const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); const iouSettled = ReportUtils.isSettled(iouReportID) || action?.childStatusNum === CONST.REPORT.STATUS_NUM.REIMBURSED; @@ -189,9 +189,8 @@ function ReportPreview({ const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction})); const showRTERViolationMessage = numberOfRequests === 1 && - TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID ?? '-1', transactionViolations)); - const shouldShowBrokenConnectionViolation = - numberOfRequests === 1 && TransactionUtils.shouldShowBrokenConnectionViolation(allTransactions.at(0)?.transactionID ?? '-1', iouReport, policy); + TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID, transactionViolations)); + const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && TransactionUtils.shouldShowBrokenConnectionViolation(allTransactions.at(0)?.transactionID, iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? TransactionUtils.getMerchant(allTransactions.at(0)) : null; const formattedDescription = numberOfRequests === 1 ? TransactionUtils.getDescription(allTransactions.at(0)) : null; @@ -500,11 +499,13 @@ function ReportPreview({ accessibilityLabel={translate('iou.viewDetails')} > - + {lastThreeReceipts.length > 0 && ( + + )} diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index f6f436cbd51e..2ea295d16143 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -75,7 +75,8 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che ? taskReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED : action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED; const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitleFromReport(taskReport, action?.childReportName ?? '')); - const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? -1; + const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID; + const taskOwnerAccountID = taskReport?.ownerAccountID ?? action?.actorAccountID ?? CONST.DEFAULT_NUMBER_ID; const hasAssignee = taskAssigneeAccountID > 0; const personalDetails = usePersonalDetails(); const avatar = personalDetails?.[taskAssigneeAccountID]?.avatar ?? Expensicons.FallbackAvatar; @@ -106,12 +107,12 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che { if (isTaskCompleted) { - Task.reopenTask(taskReport); + Task.reopenTask(taskReport, taskReportID); } else { - Task.completeTask(taskReport); + Task.completeTask(taskReport, taskReportID); } })} accessibilityLabel={translate('task.task')} diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index 2b0dc9387927..7901426b33e0 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -10,8 +10,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import Text from '@components/Text'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -27,27 +26,28 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; -type TaskViewProps = WithCurrentUserPersonalDetailsProps & { +type TaskViewProps = { /** The report currently being looked at */ report: Report; }; -function TaskView({report, ...props}: TaskViewProps) { +function TaskView({report}: TaskViewProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const personalDetails = usePersonalDetails(); useEffect(() => { Task.setTaskReport(report); }, [report]); - const personalDetails = usePersonalDetails(); const taskTitle = convertToLTR(report.reportName ?? ''); const assigneeTooltipDetails = ReportUtils.getDisplayNamesWithTooltips( OptionsListUtils.getPersonalDetailsForAccountIDs(report.managerID ? [report.managerID] : [], personalDetails), false, ); - const isCompleted = ReportUtils.isCompletedTaskReport(report); const isOpen = ReportUtils.isOpenTaskReport(report); - const canModifyTask = Task.canModifyTask(report, props.currentUserPersonalDetails.accountID); - const canActionTask = Task.canActionTask(report, props.currentUserPersonalDetails.accountID); + const isCompleted = ReportUtils.isCompletedTaskReport(report); + const canModifyTask = Task.canModifyTask(report, currentUserPersonalDetails.accountID); + const canActionTask = Task.canActionTask(report, currentUserPersonalDetails.accountID); const disableState = !canModifyTask; const isDisableInteractive = !canModifyTask || !isOpen; const {translate} = useLocalize(); @@ -77,10 +77,10 @@ function TaskView({report, ...props}: TaskViewProps) { styles.ph5, styles.pv2, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, false, disableState, !isDisableInteractive), true), - isDisableInteractive && !disableState && styles.cursorDefault, + isDisableInteractive && styles.cursorDefault, ]} - disabled={disableState} accessibilityLabel={taskTitle || translate('task.task')} + disabled={isDisableInteractive} > {({pressed}) => ( @@ -104,7 +104,7 @@ function TaskView({report, ...props}: TaskViewProps) { containerBorderRadius={8} caretSize={16} accessibilityLabel={taskTitle || translate('task.task')} - disabled={!canModifyTask || !canActionTask} + disabled={!canActionTask} /> - {isOpen && ( + {!isDisableInteractive && ( Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))} - shouldShowRightIcon={isOpen} + shouldShowRightIcon={!isDisableInteractive} disabled={disableState} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} shouldGreyOutWhenDisabled={false} numberOfLinesTitle={0} interactive={!isDisableInteractive} + shouldUseDefaultCursorWhenDisabled /> @@ -153,23 +154,25 @@ function TaskView({report, ...props}: TaskViewProps) { avatarSize={CONST.AVATAR_SIZE.SMALLER} titleStyle={styles.assigneeTextStyle} onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))} - shouldShowRightIcon={isOpen} + shouldShowRightIcon={!isDisableInteractive} disabled={disableState} wrapperStyle={[styles.pv2]} isSmallAvatarSubscriptMenu shouldGreyOutWhenDisabled={false} interactive={!isDisableInteractive} titleWithTooltips={assigneeTooltipDetails} + shouldUseDefaultCursorWhenDisabled /> ) : ( Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID, Navigation.getReportRHPActiveRoute()))} - shouldShowRightIcon={isOpen} + shouldShowRightIcon={!isDisableInteractive} disabled={disableState} wrapperStyle={[styles.pv2]} shouldGreyOutWhenDisabled={false} interactive={!isDisableInteractive} + shouldUseDefaultCursorWhenDisabled /> )} @@ -180,4 +183,4 @@ function TaskView({report, ...props}: TaskViewProps) { TaskView.displayName = 'TaskView'; -export default withCurrentUserPersonalDetails(TaskView); +export default TaskView; diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index 3a09629ffbfd..9488b57f302d 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -7,7 +7,6 @@ import {PickerAvoidingView} from 'react-native-picker-select'; import type {EdgeInsets} from 'react-native-safe-area-context'; import useEnvironment from '@hooks/useEnvironment'; import useInitialDimensions from '@hooks/useInitialWindowDimensions'; -import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; import useTackInputFocus from '@hooks/useTackInputFocus'; @@ -162,7 +161,6 @@ function ScreenWrapper( const {initialHeight} = useInitialDimensions(); const styles = useThemeStyles(); const {isDevelopment} = useEnvironment(); - const {isOffline} = useNetwork(); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const maxHeight = shouldEnableMaxHeight ? windowHeight : undefined; const minHeight = shouldEnableMinHeight && !Browser.isSafari() ? initialHeight : undefined; @@ -248,18 +246,17 @@ function ScreenWrapper( } // We always need the safe area padding bottom if we're showing the offline indicator since it is bottom-docked. - const isSafeAreaBottomPaddingApplied = includeSafeAreaPaddingBottom || (isOffline && shouldShowOfflineIndicator); - if (isSafeAreaBottomPaddingApplied) { + if (includeSafeAreaPaddingBottom) { paddingStyle.paddingBottom = paddingBottom; } - if (isSafeAreaBottomPaddingApplied && ignoreInsetsConsumption) { + if (includeSafeAreaPaddingBottom && ignoreInsetsConsumption) { paddingStyle.paddingBottom = unmodifiedPaddings.bottom; } const isAvoidingViewportScroll = useTackInputFocus(isFocused && shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && Browser.isMobileWebKit()); const contextValue = useMemo( - () => ({didScreenTransitionEnd, isSafeAreaTopPaddingApplied, isSafeAreaBottomPaddingApplied}), - [didScreenTransitionEnd, isSafeAreaBottomPaddingApplied, isSafeAreaTopPaddingApplied], + () => ({didScreenTransitionEnd, isSafeAreaTopPaddingApplied, isSafeAreaBottomPaddingApplied: includeSafeAreaPaddingBottom}), + [didScreenTransitionEnd, includeSafeAreaPaddingBottom, isSafeAreaTopPaddingApplied], ); return ( @@ -301,7 +298,14 @@ function ScreenWrapper( } {isSmallScreenWidth && shouldShowOfflineIndicator && ( <> - + {/* Since import state is tightly coupled to the offline state, it is safe to display it when showing offline indicator */} diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index a78845f126d2..21a5832052c0 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -1,3 +1,4 @@ +import {useIsFocused} from '@react-navigation/native'; import React, {useMemo, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -8,6 +9,8 @@ import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; import * as Expensicons from '@components/Icon/Expensicons'; import {usePersonalDetails} from '@components/OnyxProvider'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; +import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -55,6 +58,11 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { const [isDeleteExpensesConfirmModalVisible, setIsDeleteExpensesConfirmModalVisible] = useState(false); const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); + const isFocused = useIsFocused(); + const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP, + isFocused, + ); const {status, hash} = queryJSON; @@ -348,12 +356,25 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { shouldUseStyleUtilityForAnchorPosition /> ) : ( -