diff --git a/.eslintignore b/.eslintignore index 3d966d096add..5c4b73d8a682 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,7 @@ .github/actions/**/index.js *.config.js **/.eslintrc.js +**/.eslintrc.changed.js **/node_modules/** **/dist/** android/**/build/** diff --git a/.eslintrc.changed.js b/.eslintrc.changed.js new file mode 100644 index 000000000000..c279c3e67a51 --- /dev/null +++ b/.eslintrc.changed.js @@ -0,0 +1,10 @@ +module.exports = { + plugins: ['@typescript-eslint', 'deprecation'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + }, + rules: { + 'deprecation/deprecation': 'error', + }, +}; diff --git a/.eslintrc.pr.js b/.eslintrc.pr.js deleted file mode 100644 index 63e058bf6005..000000000000 --- a/.eslintrc.pr.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - extends: './.eslintrc', - plugins: ['deprecation'], - rules: { - 'deprecation/deprecation': 'error', - }, -}; diff --git a/.github/workflows/lint-changed.yml b/.github/workflows/lint-changed.yml new file mode 100644 index 000000000000..ddb640bfe9cf --- /dev/null +++ b/.github/workflows/lint-changed.yml @@ -0,0 +1,32 @@ +name: Changed files ESLint check + +on: + workflow_call: + pull_request: + types: [opened, synchronize] + branches-ignore: [staging, production] + paths: ['**.js', '**.ts', '**.tsx', '**.json', '**.mjs', '**.cjs', 'config/.editorconfig', '.watchmanconfig', '.imgbotconfig'] + +concurrency: + group: ${{ github.ref == 'refs/heads/main' && format('{0}-{1}', github.ref, github.sha) || github.ref }}-changed-lint + cancel-in-progress: true + +jobs: + lint-changed: + name: Changed files ESLint check + if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + + - name: Run ESLint to check for deprecation warnings + run: | + # This will just fetch the latest commit from main + git fetch origin main --no-tags --depth=1 + + # shellcheck disable=SC2046 + npm run lint-changed diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4cf5c3eb287f..af6bfa17fb6e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Lint code +name: ESLint check on: workflow_call: @@ -13,7 +13,7 @@ concurrency: jobs: lint: - name: Run ESLint + name: ESLint check if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} runs-on: ubuntu-latest steps: @@ -27,21 +27,3 @@ jobs: run: npm run lint env: CI: true - - - name: Run ESLint with stricter checks on changed files - run: | - # shellcheck disable=SC2046 - npx eslint --config ./.eslintrc.pr.js $(git diff --diff-filter=AM --name-only main -- "*.js" "*.ts" "*.tsx") - - - name: Verify there's no Prettier diff - run: | - npm run prettier -- --loglevel silent - if ! git diff --name-only --exit-code; then - # shellcheck disable=SC2016 - echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' - exit 1 - fi - - - name: Run unused style searcher - shell: bash - run: ./.github/scripts/findUnusedKeys.sh diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index e5ccdfa53076..796468170275 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -13,6 +13,9 @@ jobs: lint: uses: ./.github/workflows/lint.yml + prettier: + uses: ./.github/workflows/prettier.yml + test: uses: ./.github/workflows/test.yml diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 000000000000..d5cff2d9b838 --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,37 @@ +name: Prettier check + +on: + workflow_call: + pull_request: + types: [opened, synchronize] + branches-ignore: [staging, production] + paths: ['**.js', '**.ts', '**.tsx', '**.json', '**.mjs', '**.cjs', 'config/.editorconfig', '.watchmanconfig', '.imgbotconfig'] + +concurrency: + group: ${{ github.ref == 'refs/heads/main' && format('{0}-{1}', github.ref, github.sha) || github.ref }}-prettier + cancel-in-progress: true + +jobs: + prettier: + name: Prettier check + if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + + - name: Verify there's no Prettier diff + run: | + npm run prettier -- --loglevel silent + if ! git diff --name-only --exit-code; then + # shellcheck disable=SC2016 + echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' + exit 1 + fi + + - name: Run unused style searcher + shell: bash + run: ./.github/scripts/findUnusedKeys.sh diff --git a/android/app/build.gradle b/android/app/build.gradle index 19c634a10df8..06c990e279c0 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 1009003507 - versionName "9.0.35-7" + versionCode 1009003600 + versionName "9.0.36-0" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/android/app/src/main/java/com/expensify/chat/MainApplication.kt b/android/app/src/main/java/com/expensify/chat/MainApplication.kt index 26a28d9955a0..2cc8b7780253 100644 --- a/android/app/src/main/java/com/expensify/chat/MainApplication.kt +++ b/android/app/src/main/java/com/expensify/chat/MainApplication.kt @@ -8,6 +8,7 @@ import android.database.CursorWindow import android.os.Process import androidx.multidex.MultiDexApplication import com.expensify.chat.bootsplash.BootSplashPackage +import com.expensify.chat.shortcutManagerModule.ShortcutManagerPackage import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.facebook.react.ReactNativeHost @@ -29,6 +30,7 @@ class MainApplication : MultiDexApplication(), ReactApplication { PackageList(this).packages.apply { // Packages that cannot be autolinked yet can be added manually here, for example: // add(MyReactNativePackage()); + add(ShortcutManagerPackage()) add(BootSplashPackage()) add(ExpensifyAppPackage()) add(RNTextInputResetPackage()) diff --git a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java index 8eff32dedf76..b950921a0cd5 100644 --- a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java @@ -8,6 +8,7 @@ import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.content.Context; +import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; @@ -30,10 +31,13 @@ import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.graphics.drawable.IconCompat; import androidx.versionedparcelable.ParcelUtils; import com.expensify.chat.R; +import com.expensify.chat.shortcutManagerModule.ShortcutManagerUtils; import com.urbanairship.AirshipConfigOptions; import com.urbanairship.json.JsonMap; import com.urbanairship.json.JsonValue; @@ -47,6 +51,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -205,44 +210,47 @@ private void applyMessageStyle(@NonNull Context context, NotificationCompat.Buil // Use the formatted alert message from the backend. Otherwise fallback on the message in the Onyx data. String message = alert != null ? alert : messageData.get("message").getList().get(0).getMap().get("text").getString(); - String conversationName = payload.get("roomName") == null ? "" : payload.get("roomName").getString(""); + String roomName = payload.get("roomName") == null ? "" : payload.get("roomName").getString(""); - // create the Person object who sent the latest report comment + // Create the Person object who sent the latest report comment Bitmap personIcon = fetchIcon(context, avatar); builder.setLargeIcon(personIcon); Person person = createMessagePersonObject(IconCompat.createWithBitmap(personIcon), accountID, name); + ShortcutManagerUtils.addDynamicShortcut(context, reportID, name, accountID, personIcon, person); + // Create latest received message object long createdTimeInMillis = getMessageTimeInMillis(messageData.get("created").getString("")); NotificationCompat.MessagingStyle.Message newMessage = new NotificationCompat.MessagingStyle.Message(message, createdTimeInMillis, person); - // Conversational styling should be applied to groups chats, rooms, and any 1:1 chats with more than one notification (ensuring the large profile image is always shown) - if (!conversationName.isEmpty() || hasExistingNotification) { - // Create the messaging style notification builder for this notification, associating it with the person who sent the report comment - NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(person) - .setGroupConversation(true) - .setConversationTitle(conversationName); + NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(person); + // Add all conversation messages to the notification, including the last one we just received. + List messages; + if (hasExistingNotification) { + NotificationCompat.MessagingStyle previousStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(existingReportNotification.getNotification()); + messages = previousStyle != null ? previousStyle.getMessages() : new ArrayList<>(List.of(recreatePreviousMessage(existingReportNotification))); + } else { + messages = new ArrayList<>(); + } - // Add all conversation messages to the notification, including the last one we just received. - List messages; - if (hasExistingNotification) { - NotificationCompat.MessagingStyle previousStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(existingReportNotification.getNotification()); - messages = previousStyle != null ? previousStyle.getMessages() : new ArrayList<>(List.of(recreatePreviousMessage(existingReportNotification))); - } else { - messages = new ArrayList<>(); - } - - // add the last one message we just received. - messages.add(newMessage); + // add the last one message we just received. + messages.add(newMessage); - for (NotificationCompat.MessagingStyle.Message activeMessage : messages) { - messagingStyle.addMessage(activeMessage); - } + for (NotificationCompat.MessagingStyle.Message activeMessage : messages) { + messagingStyle.addMessage(activeMessage); + } - builder.setStyle(messagingStyle); + // Conversational styling should be applied to groups chats, rooms, and any 1:1 chats with more than one notification (ensuring the large profile image is always shown) + if (!roomName.isEmpty()) { + // Create the messaging style notification builder for this notification, associating it with the person who sent the report comment + messagingStyle + .setGroupConversation(true) + .setConversationTitle(roomName); } + builder.setStyle(messagingStyle); + builder.setShortcutId(accountID); // save reportID and person info for future merging builder.addExtras(createMessageExtrasBundle(reportID, person)); diff --git a/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerModule.java b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerModule.java new file mode 100644 index 000000000000..fdb6d0ba3b97 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerModule.java @@ -0,0 +1,43 @@ +package com.expensify.chat.shortcutManagerModule; + +import static androidx.core.app.NotificationCompat.CATEGORY_MESSAGE; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.app.Person; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; + +import java.util.Collections; + +import com.expensify.chat.customairshipextender.CustomNotificationProvider; + +public class ShortcutManagerModule extends ReactContextBaseJavaModule { + private ReactApplicationContext context; + + public ShortcutManagerModule(ReactApplicationContext context) { + super(context); + this.context = context; + } + + @NonNull + @Override + public String getName() { + return "ShortcutManager"; + } + + @ReactMethod + public void removeAllDynamicShortcuts() { + ShortcutManagerUtils.removeAllDynamicShortcuts(context); + } +} diff --git a/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerPackage.java b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerPackage.java new file mode 100644 index 000000000000..d28f75592d93 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerPackage.java @@ -0,0 +1,29 @@ +package com.expensify.chat.shortcutManagerModule; + +import androidx.annotation.NonNull; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ShortcutManagerPackage implements ReactPackage { + + @NonNull + @Override + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @NonNull + @Override + public List createNativeModules(@NonNull ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new ShortcutManagerModule(reactContext)); + return modules; + } +} diff --git a/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerUtils.java b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerUtils.java new file mode 100644 index 000000000000..5947faaa67c4 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/shortcutManagerModule/ShortcutManagerUtils.java @@ -0,0 +1,38 @@ +package com.expensify.chat.shortcutManagerModule; + +import static androidx.core.app.NotificationCompat.CATEGORY_MESSAGE; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; + +import androidx.core.app.Person; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; + +import java.util.Collections; + +public class ShortcutManagerUtils { + public static void removeAllDynamicShortcuts(Context context) { + ShortcutManagerCompat.removeAllDynamicShortcuts(context); + } + + public static void addDynamicShortcut(Context context, long reportID, String name, String accountID, Bitmap personIcon, Person person) { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse("new-expensify://r/" + reportID)); + + ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, accountID) + .setShortLabel(name) + .setLongLabel(name) + .setCategories(Collections.singleton(CATEGORY_MESSAGE)) + .setIntent(intent) + .setLongLived(true) + .setPerson(person) + .setIcon(IconCompat.createWithBitmap(personIcon)) + .build(); + ShortcutManagerCompat.pushDynamicShortcut(context, shortcutInfo); + } + +} diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Deel.md b/docs/articles/expensify-classic/connections/Deel.md similarity index 100% rename from docs/articles/expensify-classic/integrations/HR-integrations/Deel.md rename to docs/articles/expensify-classic/connections/Deel.md diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 19e80e80c59e..768062717d4b 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 0CDA8E35287DD650004ECBEC /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0CDA8E33287DD650004ECBEC /* AppDelegate.mm */; }; 0CDA8E37287DD6A0004ECBEC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CDA8E36287DD6A0004ECBEC /* Images.xcassets */; }; 0CDA8E38287DD6A0004ECBEC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CDA8E36287DD6A0004ECBEC /* Images.xcassets */; }; + 0DFC45942C884E0A00B56C91 /* RCTShortcutManagerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DFC45932C884E0A00B56C91 /* RCTShortcutManagerModule.m */; }; + 0DFC45952C884E0A00B56C91 /* RCTShortcutManagerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DFC45932C884E0A00B56C91 /* RCTShortcutManagerModule.m */; }; 0F5BE0CE252686330097D869 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0F5BE0CD252686320097D869 /* GoogleService-Info.plist */; }; 0F5E5350263B73FD004CA14F /* EnvironmentChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */; }; 0F5E5351263B73FD004CA14F /* EnvironmentChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */; }; @@ -89,7 +91,9 @@ 083353EA2B5AB22900C603C0 /* success.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = success.mp3; path = ../assets/sounds/success.mp3; sourceTree = ""; }; 0CDA8E33287DD650004ECBEC /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = NewExpensify/AppDelegate.mm; sourceTree = ""; }; 0CDA8E36287DD6A0004ECBEC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = NewExpensify/Images.xcassets; sourceTree = ""; }; - 0D3F9E814828D91464DF9D35 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = NewExpensify/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 0D3F9E814828D91464DF9D35 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = NewExpensify/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 0DFC45922C884D7900B56C91 /* RCTShortcutManagerModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTShortcutManagerModule.h; sourceTree = ""; }; + 0DFC45932C884E0A00B56C91 /* RCTShortcutManagerModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTShortcutManagerModule.m; sourceTree = ""; }; 0F5BE0CD252686320097D869 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 0F5E534E263B73D5004CA14F /* EnvironmentChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EnvironmentChecker.h; sourceTree = ""; }; 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EnvironmentChecker.m; sourceTree = ""; }; @@ -279,6 +283,8 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + 0DFC45922C884D7900B56C91 /* RCTShortcutManagerModule.h */, + 0DFC45932C884E0A00B56C91 /* RCTShortcutManagerModule.m */, 499B0DA92BE2A1C000CABFB0 /* PrivacyInfo.xcprivacy */, 374FB8D528A133A7000D84EF /* OriginImageRequestHandler.h */, 374FB8D628A133FE000D84EF /* OriginImageRequestHandler.mm */, @@ -888,6 +894,7 @@ buildActionMask = 2147483647; files = ( 0F5E5351263B73FD004CA14F /* EnvironmentChecker.m in Sources */, + 0DFC45952C884E0A00B56C91 /* RCTShortcutManagerModule.m in Sources */, 0CDA8E35287DD650004ECBEC /* AppDelegate.mm in Sources */, 7041848626A8E47D00E09F4D /* RCTStartupTimer.m in Sources */, 7F5E81F06BCCF61AD02CEA06 /* ExpoModulesProvider.swift in Sources */, @@ -899,6 +906,7 @@ buildActionMask = 2147483647; files = ( 18D050E0262400AF000D658B /* BridgingFile.swift in Sources */, + 0DFC45942C884E0A00B56C91 /* RCTShortcutManagerModule.m in Sources */, 0F5E5350263B73FD004CA14F /* EnvironmentChecker.m in Sources */, 374FB8D728A133FE000D84EF /* OriginImageRequestHandler.mm in Sources */, 7041848526A8E47D00E09F4D /* RCTStartupTimer.m in Sources */, diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 11f5430c3519..57c9164f9404 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.35 + 9.0.36 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.35.7 + 9.0.36.0 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 8d3815a7990f..251a41528e88 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.35 + 9.0.36 CFBundleSignature ???? CFBundleVersion - 9.0.35.7 + 9.0.36.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index f52205db454a..cba99258a068 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.35 + 9.0.36 CFBundleVersion - 9.0.35.7 + 9.0.36.0 NSExtension NSExtensionPointIdentifier diff --git a/ios/RCTShortcutManagerModule.h b/ios/RCTShortcutManagerModule.h new file mode 100644 index 000000000000..5d596d5e7a5e --- /dev/null +++ b/ios/RCTShortcutManagerModule.h @@ -0,0 +1,4 @@ +// RCTShortcutManagerModule.h +#import +@interface RCTShortcutManagerModule : NSObject +@end diff --git a/ios/RCTShortcutManagerModule.m b/ios/RCTShortcutManagerModule.m new file mode 100644 index 000000000000..bab19019a967 --- /dev/null +++ b/ios/RCTShortcutManagerModule.m @@ -0,0 +1,11 @@ +// RCTCalendarModule.m +// iOS doesn't have dynamic shortcuts like Android, so this module contains noop functions to prevent iOS from crashing +#import "RCTShortcutManagerModule.h" + +@implementation RCTShortcutManagerModule + +RCT_EXPORT_METHOD(removeAllDynamicShortcuts){} + +RCT_EXPORT_MODULE(ShortcutManager); + +@end diff --git a/package-lock.json b/package-lock.json index 045558520230..ecbe0a53fce4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.35-7", + "version": "9.0.36-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.35-7", + "version": "9.0.36-0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -95,7 +95,7 @@ "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.66", + "react-native-onyx": "2.0.68", "react-native-pager-view": "6.4.1", "react-native-pdf": "6.7.5", "react-native-performance": "^5.1.0", @@ -36650,9 +36650,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.66", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.66.tgz", - "integrity": "sha512-Ns9WzcAjarAUl9g/bftf2EUJYdgcb6BAraxwqBWVeGWk3dGBR1hVEvZ7p/3rpKjidJQqiM3LWBaM6DkNHoYd1g==", + "version": "2.0.68", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.68.tgz", + "integrity": "sha512-KzcG8r6oIHRZhtiGu2XtHwYLm6eTp74r4NyhIawinfJEgcd1YMC6KdrVMqd1J7zFLTuBXPhtjiugTbUhXraFag==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", diff --git a/package.json b/package.json index d000ef017763..f42323ca8091 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.35-7", + "version": "9.0.36-0", "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.", @@ -37,7 +37,7 @@ "test": "TZ=utc NODE_OPTIONS=--experimental-vm-modules jest", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=0 --cache --cache-location=node_modules/.cache/eslint", - "lint-changed": "eslint --fix $(git diff --diff-filter=AM --name-only main -- \"*.js\" \"*.ts\" \"*.tsx\")", + "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 eslint --max-warnings=0 --config ./.eslintrc.changed.js $(git diff --diff-filter=AM --name-only origin/main HEAD -- \"*.ts\" \"*.tsx\")", "lint-watch": "npx eslint-watch --watch --changed", "shellcheck": "./scripts/shellCheck.sh", "prettier": "prettier --write .", @@ -152,7 +152,7 @@ "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.66", + "react-native-onyx": "2.0.68", "react-native-pager-view": "6.4.1", "react-native-pdf": "6.7.5", "react-native-performance": "^5.1.0", diff --git a/src/components/BaseMiniContextMenuItem.tsx b/src/components/BaseMiniContextMenuItem.tsx index 6e1a1e0fd229..fa0fdb45153f 100644 --- a/src/components/BaseMiniContextMenuItem.tsx +++ b/src/components/BaseMiniContextMenuItem.tsx @@ -79,7 +79,7 @@ function BaseMiniContextMenuItem( role={CONST.ROLE.BUTTON} style={({hovered, pressed}) => [ styles.reportActionContextMenuMiniButton, - StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, isDelayButtonStateComplete)), + StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, isDelayButtonStateComplete), true), isDelayButtonStateComplete && styles.cursorDefault, ]} > diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index 97be05049e3c..d211aac7fd4c 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -82,8 +82,28 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim mixedUAStyles: {...styles.textSupporting, ...styles.textLineThrough}, contentModel: HTMLContentModel.textual, }), + blockquote: HTMLElementModel.fromCustomModel({ + tagName: 'blockquote', + contentModel: HTMLContentModel.block, + getMixedUAStyles: (tnode) => { + if (tnode.attributes.isemojisonly === undefined) { + return; + } + return styles.onlyEmojisTextLineHeight; + }, + }), }), - [styles.formError, styles.mb0, styles.colorMuted, styles.textLabelSupporting, styles.lh16, styles.textSupporting, styles.textLineThrough, styles.mutedNormalTextLabel], + [ + styles.formError, + styles.mb0, + styles.colorMuted, + styles.textLabelSupporting, + styles.lh16, + styles.textSupporting, + styles.textLineThrough, + styles.mutedNormalTextLabel, + styles.onlyEmojisTextLineHeight, + ], ); /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 1686ed1c62d2..276e364e26c1 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -221,7 +221,8 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti } }} withoutFocusOnSecondaryInteraction - activeOpacity={0.8} + activeOpacity={variables.pressDimValue} + opacityAnimationDuration={0} style={[ styles.flexRow, styles.alignItemsCenter, diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index d37bf32843d5..2524658d6ffc 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -3,7 +3,6 @@ import type {ReactElement, ReactNode} from 'react'; import React, {forwardRef, useContext, useMemo} from 'react'; import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {ActivityIndicator, View} from 'react-native'; -import type {AnimatedStyle} from 'react-native-reanimated'; import type {ValueOf} from 'type-fest'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -91,9 +90,6 @@ type MenuItemBaseProps = { /** Any additional styles to apply to the label */ labelStyle?: StyleProp; - /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle?: StyleProp>; - /** Additional styles to style the description text below the title */ descriptionTextStyle?: StyleProp; @@ -348,7 +344,6 @@ function MenuItem( containerStyle, titleStyle, labelStyle, - hoverAndPressStyle, descriptionTextStyle, badgeStyle, viewMode = CONST.OPTION_MODE.DEFAULT, @@ -574,6 +569,8 @@ function MenuItem( onPressOut={ControlSelection.unblock} onSecondaryInteraction={onSecondaryInteraction} wrapperStyle={outerWrapperStyle} + activeOpacity={variables.pressDimValue} + opacityAnimationDuration={0} style={({pressed}) => [ containerStyle, @@ -582,7 +579,6 @@ function MenuItem( !shouldRemoveBackground && StyleUtils.getButtonBackgroundColorStyle(getButtonState(focused || isHovered, pressed, success, disabled, interactive), true), ...(Array.isArray(wrapperStyle) ? wrapperStyle : [wrapperStyle]), - !focused && (isHovered || pressed) && hoverAndPressStyle, shouldGreyOutWhenDisabled && disabled && styles.buttonOpacityDisabled, isHovered && interactive && !focused && !pressed && !shouldRemoveBackground && styles.hoveredComponentBG, ] as StyleProp diff --git a/src/components/OpacityView.tsx b/src/components/OpacityView.tsx index d4a5c05167a0..f4884fd3c0f8 100644 --- a/src/components/OpacityView.tsx +++ b/src/components/OpacityView.tsx @@ -24,11 +24,24 @@ type OpacityViewProps = { */ dimmingValue?: number; + /** + * The duration of the dimming animation + * @default variables.dimAnimationDuration + */ + dimAnimationDuration?: number; + /** Whether the view needs to be rendered offscreen (for Android only) */ needsOffscreenAlphaCompositing?: boolean; }; -function OpacityView({shouldDim, children, style = [], dimmingValue = variables.hoverDimValue, needsOffscreenAlphaCompositing = false}: OpacityViewProps) { +function OpacityView({ + shouldDim, + dimAnimationDuration = variables.dimAnimationDuration, + children, + style = [], + dimmingValue = variables.hoverDimValue, + needsOffscreenAlphaCompositing = false, +}: OpacityViewProps) { const opacity = useSharedValue(1); const opacityStyle = useAnimatedStyle(() => ({ opacity: opacity.value, @@ -37,11 +50,11 @@ function OpacityView({shouldDim, children, style = [], dimmingValue = variables. React.useEffect(() => { if (shouldDim) { // eslint-disable-next-line react-compiler/react-compiler - opacity.value = withTiming(dimmingValue, {duration: 50}); + opacity.value = withTiming(dimmingValue, {duration: dimAnimationDuration}); } else { - opacity.value = withTiming(1, {duration: 50}); + opacity.value = withTiming(1, {duration: dimAnimationDuration}); } - }, [shouldDim, dimmingValue, opacity]); + }, [shouldDim, dimmingValue, opacity, dimAnimationDuration]); return ( diff --git a/src/components/PressableWithSecondaryInteraction/index.tsx b/src/components/PressableWithSecondaryInteraction/index.tsx index cbcf8523d9a4..810aa45ebf07 100644 --- a/src/components/PressableWithSecondaryInteraction/index.tsx +++ b/src/components/PressableWithSecondaryInteraction/index.tsx @@ -20,6 +20,7 @@ function PressableWithSecondaryInteraction( preventDefaultContextMenu = true, onSecondaryInteraction, activeOpacity = 1, + opacityAnimationDuration, ...rest }: PressableWithSecondaryInteractionProps, ref: PressableRef, @@ -100,6 +101,7 @@ function PressableWithSecondaryInteraction( wrapperStyle={[StyleUtils.combineStyles(DeviceCapabilities.canUseTouchScreen() ? [styles.userSelectNone, styles.noSelect] : [], inlineStyle), wrapperStyle]} onLongPress={onSecondaryInteraction ? executeSecondaryInteraction : undefined} pressDimmingValue={activeOpacity} + dimAnimationDuration={opacityAnimationDuration} ref={pressableRef} style={(state) => [StyleUtils.parseStyleFromFunction(style, state), inlineStyle]} needsOffscreenAlphaCompositing={needsOffscreenAlphaCompositing} diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index b07c867daeb3..ebe08cfab4f2 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -40,6 +40,12 @@ type PressableWithSecondaryInteractionProps = PressableWithFeedbackProps & { /** Opacity to reduce to when active */ activeOpacity?: number; + /** + * The duration of the opacity animation + * @default variables.dimAnimationDuration + */ + opacityAnimationDuration?: number; + /** Used to apply styles to the Pressable */ style?: ParsableStyle; diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 9b3b02a1abac..6ab2db05c49f 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -134,7 +134,6 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo interactive shouldStackHorizontally={false} onSecondaryInteraction={() => {}} - hoverAndPressStyle={false} titleWithTooltips={[]} brickRoadIndicator={violation ? 'error' : undefined} errorText={violationTranslation} diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index fe6831f72512..28408731d700 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -129,7 +129,6 @@ function ReservationView({reservation}: ReservationViewProps) { iconWidth={20} iconStyles={[StyleUtils.getTripReservationIconContainer(false), styles.mr3]} secondaryIconFill={theme.icon} - hoverAndPressStyle={styles.hoveredComponentBG} /> ); } diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 58dbc07aae8d..a317fccda550 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -27,14 +27,6 @@ Onyx.connect({ }, }); -let allCardsLists: OnyxCollection; - -Onyx.connect({ - key: ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, - waitForCollectionCallback: true, - callback: (value) => (allCardsLists = value), -}); - /** * @returns string with a month in MM format */ @@ -203,17 +195,6 @@ function getCardFeedIcon(cardFeed: string): IconAsset { return Illustrations.AmexCompanyCards; } -/** Checks if the Expensify Card toggle should be disabled */ -function shouldExpensifyCardToggleBeDisabled(workspaceAccountID?: number, areExpensifyCardsEnabled?: boolean): boolean { - if (!areExpensifyCardsEnabled || !workspaceAccountID) { - return false; - } - - const cardsList = allCardsLists?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`]; - - return !isEmptyObject(cardsList); -} - function getCardDetailsImage(cardFeed: string): IconAsset { if (cardFeed.startsWith(CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD)) { return Illustrations.MasterCardCompanyCardDetail; @@ -262,7 +243,6 @@ export { getEligibleBankAccountsForCard, sortCardsByCardholderName, getCardFeedIcon, - shouldExpensifyCardToggleBeDisabled, getCardDetailsImage, getMemberCards, }; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index f2461f400678..5e7d6428d5bb 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -47,6 +47,7 @@ import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type ReactComponentModule from '@src/types/utils/ReactComponentModule'; import CENTRAL_PANE_SCREENS from './CENTRAL_PANE_SCREENS'; import createCustomStackNavigator from './createCustomStackNavigator'; @@ -150,7 +151,7 @@ Onyx.connect({ // If the current timezone is different than the user's timezone, and their timezone is set to automatic // then update their timezone. - if (timezone?.automatic && timezone?.selected !== currentTimezone) { + if (!isEmptyObject(currentTimezone) && timezone?.automatic && timezone?.selected !== currentTimezone) { timezone.selected = currentTimezone; PersonalDetails.updateAutomaticTimezone({ automatic: true, diff --git a/src/libs/Notification/PushNotification/index.native.ts b/src/libs/Notification/PushNotification/index.native.ts index 334317ec5d0a..448365c1cd1d 100644 --- a/src/libs/Notification/PushNotification/index.native.ts +++ b/src/libs/Notification/PushNotification/index.native.ts @@ -2,6 +2,7 @@ import type {PushPayload} from '@ua/react-native-airship'; import Airship, {EventType} from '@ua/react-native-airship'; import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; +import ShortcutManager from '@libs/ShortcutManager'; import * as PushNotificationActions from '@userActions/PushNotification'; import ONYXKEYS from '@src/ONYXKEYS'; import ForegroundNotifications from './ForegroundNotifications'; @@ -139,6 +140,7 @@ const deregister: Deregister = () => { Airship.removeAllListeners(EventType.PushReceived); Airship.removeAllListeners(EventType.NotificationResponse); ForegroundNotifications.disableForegroundNotifications(); + ShortcutManager.removeAllDynamicShortcuts(); }; /** diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 8d61ad864409..1a6d33aa9f7f 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -971,6 +971,20 @@ function isTaskAction(reportAction: OnyxEntry): boolean { ); } +/** + * @param actionName - The name of the action + * @returns - Whether the action is a tag modification action + * */ +function isTagModificationAction(actionName: string): boolean { + return ( + actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_TAG || + actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_TAG_ENABLED || + actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_TAG_NAME || + actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_TAG || + actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_TAG + ); +} + // Get all IOU report actions for the report. const iouRequestTypes = new Set>([ CONST.IOU.REPORT_ACTION_TYPE.CREATE, @@ -1801,6 +1815,7 @@ export { isApprovedAction, isForwardedAction, isWhisperActionTargetedToOthers, + isTagModificationAction, shouldHideNewMarker, shouldReportActionBeVisible, shouldReportActionBeVisibleAsLastAction, diff --git a/src/libs/ShortcutManager/index.ts b/src/libs/ShortcutManager/index.ts new file mode 100644 index 000000000000..cc748caaa0de --- /dev/null +++ b/src/libs/ShortcutManager/index.ts @@ -0,0 +1,14 @@ +import {NativeModules} from 'react-native'; + +type ShortcutManagerModule = { + removeAllDynamicShortcuts: () => void; +}; + +const {ShortcutManager} = NativeModules; + +export type {ShortcutManagerModule}; + +export default ShortcutManager || + ({ + removeAllDynamicShortcuts: () => {}, + } as ShortcutManagerModule); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index f2e97607a05c..0908892dc376 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -436,7 +436,7 @@ function getOptionData({ result.alternateText = ReportActionsUtils.getCardIssuedMessage(lastAction); } else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && lastActorDisplayName && lastMessageTextFromReport) { result.alternateText = ReportUtils.formatReportLastMessageText(Parser.htmlToText(`${lastActorDisplayName}: ${lastMessageText}`)); - } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_TAG) { + } else if (ReportActionsUtils.isTagModificationAction(lastAction?.actionName)) { result.alternateText = PolicyUtils.getCleanedTagName(ReportActionsUtils.getReportActionMessage(lastAction)?.text ?? ''); } else if (lastAction && ReportActionsUtils.isOldDotReportAction(lastAction)) { result.alternateText = ReportActionsUtils.getMessageOfOldDotReportAction(lastAction); diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index dc33d82b2a04..81f3e6be36ad 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -60,7 +60,6 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro iconHeight: variables.menuIconSize, iconStyles: [styles.mh3], wrapperStyle: [styles.purposeMenuItem], - hoverAndPressStyle: [styles.purposeMenuItemSelected], numberOfLinesTitle: 0, onPress: () => { Welcome.setOnboardingPurposeSelected(choice); diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index a76d02a27b75..19e631098e40 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -223,7 +223,8 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { (isPolicyExpenseChat && isAdmin) || accountID === session?.accountID || pendingChatMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || - details.accountID === report.ownerAccountID; + details.accountID === report.ownerAccountID || + details.isOptimisticPersonalDetail; result.push({ keyForList: String(accountID), diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 0e59aa969b66..a773ef674543 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -243,7 +243,6 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { iconHeight={variables.iconSizeNormal} wrapperStyle={styles.sectionMenuItem} focused={index === activeItemIndex} - hoverAndPressStyle={styles.hoveredComponentBG} onPress={onPress} isPaneMenu /> diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 8ea9a3c6aa40..8b83b3f00ce1 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -655,7 +655,7 @@ function ReportActionItem({ children = ; } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION)) { children = ; - } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_TAG) { + } else if (ReportActionsUtils.isTagModificationAction(action.actionName)) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_NAME) { children = ; diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx index 9d1a4d9ad4b7..2e64af6c534e 100644 --- a/src/pages/home/report/comment/TextCommentFragment.tsx +++ b/src/pages/home/report/comment/TextCommentFragment.tsx @@ -61,7 +61,11 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so const editedTag = fragment?.isEdited ? `` : ''; const htmlWithDeletedTag = styleAsDeleted ? `${html}` : html; - const htmlContent = containsOnlyEmojis ? Str.replaceAll(htmlWithDeletedTag, '', '') : htmlWithDeletedTag; + let htmlContent = htmlWithDeletedTag; + if (containsOnlyEmojis) { + htmlContent = Str.replaceAll(htmlContent, '', ''); + htmlContent = Str.replaceAll(htmlContent, '
', '
'); + } let htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent; if (styleAsMuted) { diff --git a/src/pages/home/sidebar/AllSettingsScreen.tsx b/src/pages/home/sidebar/AllSettingsScreen.tsx index c0322fc0fcf7..c94c7012e411 100644 --- a/src/pages/home/sidebar/AllSettingsScreen.tsx +++ b/src/pages/home/sidebar/AllSettingsScreen.tsx @@ -88,7 +88,6 @@ function AllSettingsScreen({policies}: AllSettingsScreenProps) { wrapperStyle: styles.sectionMenuItem, isPaneMenu: true, focused: item.focused, - hoverAndPressStyle: styles.hoveredComponentBG, brickRoadIndicator: item.brickRoadIndicator, })); }, [shouldUseNarrowLayout, policies, privateSubscription, waitForNavigate, translate, styles, allConnectionSyncProgresses]); diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 069f86ce0899..a03c40e8c1f3 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -227,7 +227,7 @@ function IOURequestStepAmount({ backendAmount, currency, transaction?.created ?? '', - '', + CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, currentUserPersonalDetails.login, currentUserPersonalDetails.accountID, participants[0], @@ -242,7 +242,7 @@ function IOURequestStepAmount({ backendAmount, currency ?? 'USD', transaction?.created ?? '', - '', + CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, currentUserPersonalDetails.login, currentUserPersonalDetails.accountID, participants[0], diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 30f4cf010f70..ed5da9754250 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -333,7 +333,6 @@ function InitialSettingsPage({userWallet, bankAccountList, fundList, walletTerms shouldStackHorizontally={item.shouldStackHorizontally} floatRightAvatarSize={item.avatarSize} ref={popoverAnchor} - hoverAndPressStyle={styles.hoveredComponentBG} shouldBlockSelection={!!item.link} onSecondaryInteraction={item.link ? (event) => openPopover(item.link, event) : undefined} focused={ @@ -350,19 +349,7 @@ function InitialSettingsPage({userWallet, bankAccountList, fundList, walletTerms ); }, - [ - styles.pb4, - styles.mh3, - styles.sectionTitle, - styles.sectionMenuItem, - styles.hoveredComponentBG, - translate, - userWallet?.currentBalance, - isExecuting, - singleExecution, - activeCentralPaneRoute, - waitForNavigate, - ], + [styles.pb4, styles.mh3, styles.sectionTitle, styles.sectionMenuItem, translate, userWallet?.currentBalance, isExecuting, singleExecution, activeCentralPaneRoute, waitForNavigate], ); const accountMenuItems = useMemo(() => getMenuItemsSection(accountMenuItemsData), [accountMenuItemsData, getMenuItemsSection]); diff --git a/src/pages/settings/Profile/TimezoneInitialPage.tsx b/src/pages/settings/Profile/TimezoneInitialPage.tsx index 433a49787681..1d2e4b62a2de 100644 --- a/src/pages/settings/Profile/TimezoneInitialPage.tsx +++ b/src/pages/settings/Profile/TimezoneInitialPage.tsx @@ -14,6 +14,7 @@ import * as PersonalDetails from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; type TimezoneInitialPageProps = WithCurrentUserPersonalDetailsProps; @@ -23,6 +24,8 @@ function TimezoneInitialPage({currentUserPersonalDetails}: TimezoneInitialPagePr const {translate} = useLocalize(); + const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone as SelectedTimezone; + /** * Updates setting for automatic timezone selection. * Note: If we are updating automatically, we'll immediately calculate the user's timezone. @@ -30,7 +33,7 @@ function TimezoneInitialPage({currentUserPersonalDetails}: TimezoneInitialPagePr const updateAutomaticTimezone = (isAutomatic: boolean) => { PersonalDetails.updateAutomaticTimezone({ automatic: isAutomatic, - selected: isAutomatic ? (Intl.DateTimeFormat().resolvedOptions().timeZone as SelectedTimezone) : timezone.selected, + selected: isAutomatic && !isEmptyObject(currentTimezone) ? currentTimezone : timezone.selected, }); }; diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 797b28de4107..a2761309cbdf 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -175,7 +175,6 @@ function CardSection() { title={translate('subscription.cardSection.viewPaymentHistory')} titleStyle={styles.textStrong} onPress={() => Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery()}))} - hoverAndPressStyle={styles.hoveredComponentBG} /> )} diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 5f77ac33becf..b28b88e1ba83 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -330,13 +330,12 @@ function PaymentMethodList({ title={translate('walletPage.addBankAccount')} icon={Expensicons.Plus} wrapperStyle={[styles.paymentMethod, listItemStyle]} - hoverAndPressStyle={styles.hoveredComponentBG} ref={buttonRef} disabled={!isUserValidated} /> ), - [onPress, translate, styles.paymentMethod, styles.hoveredComponentBG, listItemStyle, buttonRef, isUserValidated], + [onPress, translate, styles.paymentMethod, listItemStyle, buttonRef, isUserValidated], ); /** @@ -365,7 +364,6 @@ function PaymentMethodList({ wrapperStyle={[styles.paymentMethod, listItemStyle]} iconRight={item.iconRight} badgeStyle={styles.badgeBordered} - hoverAndPressStyle={styles.hoveredComponentBG} shouldShowRightIcon={item.shouldShowRightIcon} shouldShowSelectedState={shouldShowSelectedState} isSelected={selectedMethodID.toString() === item.methodID?.toString()} @@ -376,7 +374,7 @@ function PaymentMethodList({ ), - [styles.ph6, styles.paymentMethod, styles.badgeBordered, styles.hoveredComponentBG, filteredPaymentMethods, translate, listItemStyle, shouldShowSelectedState, selectedMethodID], + [styles.ph6, styles.paymentMethod, styles.badgeBordered, filteredPaymentMethods, translate, listItemStyle, shouldShowSelectedState, selectedMethodID], ); return ( diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index 49c27d4e3c67..a7bd270a7a7f 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -485,7 +485,6 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { title={translate('common.transferBalance')} icon={Expensicons.Transfer} onPress={triggerKYCFlow} - hoverAndPressStyle={styles.hoveredComponentBG} shouldShowRightIcon disabled={network.isOffline} wrapperStyle={[ @@ -530,7 +529,6 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { disabled={network.isOffline} title={translate('walletPage.enableWallet')} icon={Expensicons.Wallet} - hoverAndPressStyle={styles.hoveredComponentBG} wrapperStyle={[ styles.transferBalance, shouldUseNarrowLayout ? styles.mhn5 : styles.mhn8, diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 3c2bbe15a2d0..fd7a45e31acb 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -402,7 +402,6 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyCategories wrapperStyle={styles.sectionMenuItem} highlighted={enabledItem?.routeName === item.routeName} focused={!!(item.routeName && activeRoute?.startsWith(item.routeName))} - hoverAndPressStyle={styles.hoveredComponentBG} isPaneMenu /> ))} diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index bd6f9ce158f3..0182f1ea8827 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -15,7 +15,6 @@ import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as CardUtils from '@libs/CardUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; @@ -72,6 +71,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro !!policy?.connections?.netsuite?.options?.config?.syncOptions?.syncTax; const policyID = policy?.id; const workspaceAccountID = policy?.workspaceAccountID ?? -1; + const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID.toString()}_${CONST.EXPENSIFY_CARD.BANK}`); const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID.toString()}`); const [isOrganizeWarningModalOpen, setIsOrganizeWarningModalOpen] = useState(false); const [isIntegrateWarningModalOpen, setIsIntegrateWarningModalOpen] = useState(false); @@ -110,7 +110,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro subtitleTranslationKey: 'workspace.moreFeatures.expensifyCard.subtitle', isActive: policy?.areExpensifyCardsEnabled ?? false, pendingAction: policy?.pendingFields?.areExpensifyCardsEnabled, - disabled: CardUtils.shouldExpensifyCardToggleBeDisabled(policy?.workspaceAccountID, policy?.areExpensifyCardsEnabled), + disabled: !isEmptyObject(cardsList), action: (isEnabled: boolean) => { if (!policyID) { return; diff --git a/src/styles/index.ts b/src/styles/index.ts index 9dfd18b641e8..3d91d614f722 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4324,10 +4324,6 @@ const styles = (theme: ThemeColors) => marginBottom: 8, }, - purposeMenuItemSelected: { - backgroundColor: theme.activeComponentBG, - }, - willChangeTransform: { willChange: 'transform', }, diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 478369c9489c..d3d31cc2ea1b 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1311,7 +1311,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ getButtonBackgroundColorStyle: (buttonState: ButtonStateName = CONST.BUTTON_STATES.DEFAULT, isMenuItem = false): ViewStyle => { switch (buttonState) { case CONST.BUTTON_STATES.PRESSED: - return {backgroundColor: theme.buttonPressedBG}; + return isMenuItem ? {backgroundColor: theme.buttonHoveredBG} : {backgroundColor: theme.buttonPressedBG}; case CONST.BUTTON_STATES.ACTIVE: return isMenuItem ? {backgroundColor: theme.border} : {backgroundColor: theme.buttonHoveredBG}; case CONST.BUTTON_STATES.DISABLED: diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 2a84efc72814..300574514e59 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -221,6 +221,7 @@ export default { googleEmptyListViewHeight: 14, hoverDimValue: 1, pressDimValue: 0.8, + dimAnimationDuration: 50, qrShareHorizontalPadding: 32, menuIconSize: 48, diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 7e3f299ad938..40c5b72ce1e4 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -2,6 +2,7 @@ import type {TargetedEvent} from 'react-native'; import type {BootSplashModule} from '@libs/BootSplash/types'; import type {EnvironmentCheckerModule} from '@libs/Environment/betaChecker/types'; +import type {ShortcutManagerModule} from '@libs/ShortcutManager'; import type StartupTimer from '@libs/StartupTimer/types'; type HybridAppModule = { @@ -42,6 +43,7 @@ declare module 'react-native' { StartupTimer: StartupTimer; RNTextInputReset: RNTextInputResetModule; EnvironmentChecker: EnvironmentCheckerModule; + ShortcutManager: ShortcutManagerModule; } namespace Animated {