diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index b4d8c2181b0b..94065c5b9d19 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -1,4 +1,5 @@ + #03D47C #FFFFFF #03D47C diff --git a/ios/NewExpensify/RCTBootSplash.h b/ios/NewExpensify/RCTBootSplash.h index df38a5eb35bf..c25c676feb4a 100644 --- a/ios/NewExpensify/RCTBootSplash.h +++ b/ios/NewExpensify/RCTBootSplash.h @@ -10,6 +10,7 @@ @interface RCTBootSplash : NSObject ++ (void)invalidateBootSplash; + (void)initWithStoryboard:(NSString * _Nonnull)storyboardName rootView:(RCTRootView * _Nullable)rootView; diff --git a/ios/NewExpensify/RCTBootSplash.m b/ios/NewExpensify/RCTBootSplash.m index bceac70efdcf..6c2baaed4ee0 100644 --- a/ios/NewExpensify/RCTBootSplash.m +++ b/ios/NewExpensify/RCTBootSplash.m @@ -26,6 +26,12 @@ - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } ++ (void)invalidateBootSplash { + _resolverQueue = nil; + _rootView = nil; + _nativeHidden = false; +} + + (void)initWithStoryboard:(NSString * _Nonnull)storyboardName rootView:(RCTRootView * _Nullable)rootView { if (rootView == nil || _rootView != nil || RCTRunningInAppExtension()) @@ -102,6 +108,9 @@ + (void)onContentDidAppear { block:^(NSTimer * _Nonnull timer) { [timer invalidate]; + if (_rootView == nil) + return; + if (_resolverQueue == nil) _resolverQueue = [[NSMutableArray alloc] init]; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ad1826d1c8e6..1664c982ce50 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1453,7 +1453,7 @@ PODS: - SDWebImage/Core (~> 5.17) - SocketRocket (0.6.1) - Turf (2.7.0) - - VisionCamera (2.16.2): + - VisionCamera (2.16.5): - React - React-callinvoker - React-Core @@ -1980,8 +1980,8 @@ SPEC CHECKSUMS: SDWebImageWebPCoder: af09429398d99d524cae2fe00f6f0f6e491ed102 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 - VisionCamera: 7d13aae043ffb38b224a0f725d1e23ca9c190fe7 - Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 + VisionCamera: fda554d8751e395effcc87749f8b7c198c1031be + Yoga: 13c8ef87792450193e117976337b8527b49e8c03 PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2 diff --git a/package-lock.json b/package-lock.json index 3420537495cd..072684aa77c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@kie/mock-github": "^1.0.0", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "8.3.0", - "@react-native-async-storage/async-storage": "^1.19.5", + "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-clipboard/clipboard": "^1.12.1", "@react-native-community/geolocation": "^3.0.6", @@ -113,7 +113,7 @@ "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "3.8.0", - "react-native-vision-camera": "^2.16.2", + "react-native-vision-camera": "2.16.5", "react-native-web": "^0.19.9", "react-native-web-linear-gradient": "^1.1.2", "react-native-webview": "13.6.3", @@ -45329,9 +45329,9 @@ } }, "node_modules/react-native-vision-camera": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-2.16.2.tgz", - "integrity": "sha512-QIpG33l3QB0AkTfX/ccRknwNRu1APNUkokVKF1lpRO2+tBnkXnGL0UapgXg5u9KIONZtrpupeDeO+J5B2TeQVw==", + "version": "2.16.5", + "resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-2.16.5.tgz", + "integrity": "sha512-MzXhNd597OyMQSEGhqWI4DufWkdr7PR7U9B30E3gXnln7cnvjMVIp4j3eIW9BIrgvEyUcEeL7nZM5NLhTmO/fA==", "peerDependencies": { "react": "*", "react-native": "*" diff --git a/package.json b/package.json index df55afd56e40..e08a8bdcb70a 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@kie/mock-github": "^1.0.0", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "8.3.0", - "@react-native-async-storage/async-storage": "^1.19.5", + "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-clipboard/clipboard": "^1.12.1", "@react-native-community/geolocation": "^3.0.6", @@ -161,7 +161,7 @@ "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "3.8.0", - "react-native-vision-camera": "^2.16.2", + "react-native-vision-camera": "2.16.5", "react-native-web": "^0.19.9", "react-native-web-linear-gradient": "^1.1.2", "react-native-webview": "13.6.3", diff --git a/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch b/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch index c613a47a3072..d5a390daf201 100644 --- a/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch +++ b/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch @@ -7,13 +7,13 @@ index 3a1a548..fe030bb 100644 android { - compileSdkVersion 28 -+ compileSdkVersion 30 ++ compileSdkVersion 34 buildToolsVersion "28.0.3" defaultConfig { minSdkVersion 16 - targetSdkVersion 28 -+ targetSdkVersion 30 ++ targetSdkVersion 34 versionCode 1 versionName "1.0" } diff --git a/patches/@react-native-camera-roll+camera-roll+5.4.0.patch b/patches/@react-native-camera-roll+camera-roll+5.4.0.patch new file mode 100644 index 000000000000..f0429bc10125 --- /dev/null +++ b/patches/@react-native-camera-roll+camera-roll+5.4.0.patch @@ -0,0 +1,15 @@ +diff --git a/node_modules/@react-native-camera-roll/camera-roll/android/build.gradle b/node_modules/@react-native-camera-roll/camera-roll/android/build.gradle +index 3f76132..63dc946 100644 +--- a/node_modules/@react-native-camera-roll/camera-roll/android/build.gradle ++++ b/node_modules/@react-native-camera-roll/camera-roll/android/build.gradle +@@ -81,7 +81,9 @@ def findNodeModulePath(baseDir, packageName) { + } + + def resolveReactNativeDirectory() { +- def reactNative = file("${findNodeModulePath(rootProject.projectDir, "react-native")}") ++ def projectDir = this.hasProperty('reactNativeProject') ? this.reactNativeProject : rootProject.projectDir ++ def modulePath = file(projectDir); ++ def reactNative = file("${findNodeModulePath(modulePath, 'react-native')}") + if (reactNative.exists()) { + return reactNative + } diff --git a/patches/@react-native-community+cli-platform-android+12.3.0.patch b/patches/@react-native-community+cli-platform-android+12.3.0.patch new file mode 100644 index 000000000000..d94baf0f9c6e --- /dev/null +++ b/patches/@react-native-community+cli-platform-android+12.3.0.patch @@ -0,0 +1,52 @@ +diff --git a/node_modules/@react-native-community/cli-platform-android/native_modules.gradle b/node_modules/@react-native-community/cli-platform-android/native_modules.gradle +index bbfa7f7..ed53872 100644 +--- a/node_modules/@react-native-community/cli-platform-android/native_modules.gradle ++++ b/node_modules/@react-native-community/cli-platform-android/native_modules.gradle +@@ -140,6 +140,7 @@ class ReactNativeModules { + private Logger logger + private String packageName + private File root ++ private File rnRoot + private ArrayList> reactNativeModules + private ArrayList unstable_reactLegacyComponentNames + private HashMap reactNativeModulesBuildVariants +@@ -147,9 +148,10 @@ class ReactNativeModules { + + private static String LOG_PREFIX = ":ReactNative:" + +- ReactNativeModules(Logger logger, File root) { ++ ReactNativeModules(Logger logger, File root, File rnRoot) { + this.logger = logger + this.root = root ++ this.rnRoot = rnRoot + + def (nativeModules, reactNativeModulesBuildVariants, androidProject, reactNativeVersion) = this.getReactNativeConfig() + this.reactNativeModules = nativeModules +@@ -416,10 +418,10 @@ class ReactNativeModules { + */ + def cliResolveScript = "try {console.log(require('@react-native-community/cli').bin);} catch (e) {console.log(require('react-native/cli').bin);}" + String[] nodeCommand = ["node", "-e", cliResolveScript] +- def cliPath = this.getCommandOutput(nodeCommand, this.root) ++ def cliPath = this.getCommandOutput(nodeCommand, this.rnRoot) + + String[] reactNativeConfigCommand = ["node", cliPath, "config"] +- def reactNativeConfigOutput = this.getCommandOutput(reactNativeConfigCommand, this.root) ++ def reactNativeConfigOutput = this.getCommandOutput(reactNativeConfigCommand, this.rnRoot) + + def json + try { +@@ -486,7 +488,13 @@ class ReactNativeModules { + */ + def projectRoot = rootProject.projectDir + +-def autoModules = new ReactNativeModules(logger, projectRoot) ++def autoModules ++ ++if(this.hasProperty('reactNativeProject')){ ++ autoModules = new ReactNativeModules(logger, projectRoot, new File(projectRoot, reactNativeProject)) ++} else { ++ autoModules = new ReactNativeModules(logger, projectRoot, projectRoot) ++} + + def reactNativeVersionRequireNewArchEnabled(autoModules) { + def rnVersion = autoModules.reactNativeVersion diff --git a/patches/@react-native-community+cli-platform-ios+12.3.0.patch b/patches/@react-native-community+cli-platform-ios+12.3.0.patch new file mode 100644 index 000000000000..cfae504e44fa --- /dev/null +++ b/patches/@react-native-community+cli-platform-ios+12.3.0.patch @@ -0,0 +1,52 @@ +diff --git a/node_modules/@react-native-community/cli-platform-ios/native_modules.rb b/node_modules/@react-native-community/cli-platform-ios/native_modules.rb +index 82f537c..f5e2cda 100644 +--- a/node_modules/@react-native-community/cli-platform-ios/native_modules.rb ++++ b/node_modules/@react-native-community/cli-platform-ios/native_modules.rb +@@ -12,7 +12,7 @@ + require 'pathname' + require 'cocoapods' + +-def use_native_modules!(config = nil) ++def updateConfig(config = nil) + if (config.is_a? String) + Pod::UI.warn("Passing custom root to use_native_modules! is deprecated.", + [ +@@ -24,7 +24,6 @@ def use_native_modules!(config = nil) + # Resolving the path the RN CLI. The `@react-native-community/cli` module may not be there for certain package managers, so we fall back to resolving it through `react-native` package, that's always present in RN projects + cli_resolve_script = "try {console.log(require('@react-native-community/cli').bin);} catch (e) {console.log(require('react-native/cli').bin);}" + cli_bin = Pod::Executable.execute_command("node", ["-e", cli_resolve_script], true).strip +- + if (!config) + json = [] + +@@ -36,10 +35,30 @@ def use_native_modules!(config = nil) + + config = JSON.parse(json.join("\n")) + end ++end ++ ++def use_native_modules!(config = nil) ++ if (ENV['REACT_NATIVE_DIR']) ++ Dir.chdir(ENV['REACT_NATIVE_DIR']) do ++ config = updateConfig(config) ++ end ++ else ++ config = updateConfig(config) ++ end + + project_root = Pathname.new(config["project"]["ios"]["sourceDir"]) + ++ if(ENV["PROJECT_ROOT_DIR"]) ++ project_root = File.join(Dir.pwd, ENV["PROJECT_ROOT_DIR"]) ++ ++ end ++ + packages = config["dependencies"] ++ ++ if (ENV["NO_FLIPPER"]) ++ packages = {**packages, "react-native-flipper" => {"platforms" => {"ios" => nil}}} ++ end ++ + found_pods = [] + + packages.each do |package_name, package| diff --git a/patches/@react-native-firebase+analytics+12.9.3.patch b/patches/@react-native-firebase+analytics+12.9.3.patch new file mode 100644 index 000000000000..74d3e2a8005a --- /dev/null +++ b/patches/@react-native-firebase+analytics+12.9.3.patch @@ -0,0 +1,18 @@ +diff --git a/node_modules/@react-native-firebase/analytics/android/build.gradle b/node_modules/@react-native-firebase/analytics/android/build.gradle +index d223ebf..821b730 100644 +--- a/node_modules/@react-native-firebase/analytics/android/build.gradle ++++ b/node_modules/@react-native-firebase/analytics/android/build.gradle +@@ -45,6 +45,8 @@ if (coreVersionDetected != coreVersionRequired) { + } + } + ++apply plugin: 'com.android.library' ++ + project.ext { + set('react-native', [ + versions: [ +@@ -144,4 +146,3 @@ dependencies { + ReactNative.shared.applyPackageVersion() + ReactNative.shared.applyDefaultExcludes() + ReactNative.module.applyAndroidVersions() +-ReactNative.module.applyReactNativeDependency("api") diff --git a/patches/@react-native-firebase+app+12.9.3.patch b/patches/@react-native-firebase+app+12.9.3.patch new file mode 100644 index 000000000000..312fdacf4432 --- /dev/null +++ b/patches/@react-native-firebase+app+12.9.3.patch @@ -0,0 +1,25 @@ +diff --git a/node_modules/@react-native-firebase/app/android/build.gradle b/node_modules/@react-native-firebase/app/android/build.gradle +index 05f629a..7c36693 100644 +--- a/node_modules/@react-native-firebase/app/android/build.gradle ++++ b/node_modules/@react-native-firebase/app/android/build.gradle +@@ -18,6 +18,7 @@ buildscript { + + plugins { + id "io.invertase.gradle.build" version "1.5" ++ id 'com.android.library' + } + + def packageJson = PackageJson.getForProject(project) +@@ -91,6 +92,7 @@ repositories { + } + + dependencies { ++ api 'com.facebook.react:react-native:+' + implementation platform("com.google.firebase:firebase-bom:${ReactNative.ext.getVersion("firebase", "bom")}") + implementation "com.google.firebase:firebase-common" + implementation "com.google.android.gms:play-services-auth:${ReactNative.ext.getVersion("play", "play-services-auth")}" +@@ -99,4 +101,3 @@ dependencies { + ReactNative.shared.applyPackageVersion() + ReactNative.shared.applyDefaultExcludes() + ReactNative.module.applyAndroidVersions() +-ReactNative.module.applyReactNativeDependency("api") diff --git a/patches/@react-native-firebase+crashlytics+12.9.3.patch b/patches/@react-native-firebase+crashlytics+12.9.3.patch new file mode 100644 index 000000000000..560f462731dc --- /dev/null +++ b/patches/@react-native-firebase+crashlytics+12.9.3.patch @@ -0,0 +1,17 @@ +diff --git a/node_modules/@react-native-firebase/crashlytics/android/build.gradle b/node_modules/@react-native-firebase/crashlytics/android/build.gradle +index 6b6de57..9b89ae7 100644 +--- a/node_modules/@react-native-firebase/crashlytics/android/build.gradle ++++ b/node_modules/@react-native-firebase/crashlytics/android/build.gradle +@@ -18,6 +18,7 @@ buildscript { + + plugins { + id "io.invertase.gradle.build" version "1.5" ++ id 'com.android.library' + } + + def appProject +@@ -92,4 +93,3 @@ dependencies { + ReactNative.shared.applyPackageVersion() + ReactNative.shared.applyDefaultExcludes() + ReactNative.module.applyAndroidVersions() +-ReactNative.module.applyReactNativeDependency("api") diff --git a/patches/@react-native-firebase+perf+12.9.3.patch b/patches/@react-native-firebase+perf+12.9.3.patch new file mode 100644 index 000000000000..7d8a9f4f23b5 --- /dev/null +++ b/patches/@react-native-firebase+perf+12.9.3.patch @@ -0,0 +1,17 @@ +diff --git a/node_modules/@react-native-firebase/perf/android/build.gradle b/node_modules/@react-native-firebase/perf/android/build.gradle +index b4a9c7b..5835e3d 100644 +--- a/node_modules/@react-native-firebase/perf/android/build.gradle ++++ b/node_modules/@react-native-firebase/perf/android/build.gradle +@@ -19,6 +19,7 @@ buildscript { + + plugins { + id "io.invertase.gradle.build" version "1.5" ++ id 'com.android.library' + } + + def appProject +@@ -129,4 +130,3 @@ dependencies { + ReactNative.shared.applyPackageVersion() + ReactNative.shared.applyDefaultExcludes() + ReactNative.module.applyAndroidVersions() +-ReactNative.module.applyReactNativeDependency("api") diff --git a/patches/expo+50.0.4.patch b/patches/expo+50.0.4.patch new file mode 100644 index 000000000000..95157e1d73c6 --- /dev/null +++ b/patches/expo+50.0.4.patch @@ -0,0 +1,10 @@ +diff --git a/node_modules/expo/scripts/autolinking.gradle b/node_modules/expo/scripts/autolinking.gradle +index 60d6ef8..3ed90a4 100644 +--- a/node_modules/expo/scripts/autolinking.gradle ++++ b/node_modules/expo/scripts/autolinking.gradle +@@ -1,4 +1,4 @@ + // Resolve `expo` > `expo-modules-autolinking` dependency chain + def autolinkingPath = ["node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim() +-apply from: new File(autolinkingPath, "../scripts/android/autolinking_implementation.gradle"); + ++apply from: hasProperty("reactNativeProject") ? file('../../expo-modules-autolinking/scripts/android/autolinking_implementation.gradle') : new File(autolinkingPath, "../scripts/android/autolinking_implementation.gradle"); diff --git a/patches/expo-modules-autolinking+1.10.2.patch b/patches/expo-modules-autolinking+1.10.2.patch new file mode 100644 index 000000000000..4b68007ba125 --- /dev/null +++ b/patches/expo-modules-autolinking+1.10.2.patch @@ -0,0 +1,40 @@ +diff --git a/node_modules/expo-modules-autolinking/scripts/android/autolinking_implementation.gradle b/node_modules/expo-modules-autolinking/scripts/android/autolinking_implementation.gradle +index 92f1fd6..ada01ad 100644 +--- a/node_modules/expo-modules-autolinking/scripts/android/autolinking_implementation.gradle ++++ b/node_modules/expo-modules-autolinking/scripts/android/autolinking_implementation.gradle +@@ -149,12 +149,13 @@ class ExpoAutolinkingManager { + } + + static private String[] convertOptionsToCommandArgs(String command, Map options) { ++ def expoPath = options.searchPaths ? "../react-native/node_modules/expo" : "expo" + String[] args = [ + 'node', + '--no-warnings', + '--eval', + // Resolve the `expo` > `expo-modules-autolinking` chain from the project root +- 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo\')] }))(process.argv.slice(1))', ++ "require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'${expoPath}\')] }))(process.argv.slice(1))", + '--', + command, + '--platform', +diff --git a/node_modules/expo-modules-autolinking/scripts/ios/project_integrator.rb b/node_modules/expo-modules-autolinking/scripts/ios/project_integrator.rb +index 5d46f1e..3db7b89 100644 +--- a/node_modules/expo-modules-autolinking/scripts/ios/project_integrator.rb ++++ b/node_modules/expo-modules-autolinking/scripts/ios/project_integrator.rb +@@ -215,6 +215,7 @@ module Expo + args = autolinking_manager.base_command_args.map { |arg| "\"#{arg}\"" } + platform = autolinking_manager.platform_name.downcase + package_names = autolinking_manager.packages_to_generate.map { |package| "\"#{package.name}\"" } ++ expo_path = ENV['REACT_NATIVE_DIR'] ? "#{ENV['REACT_NATIVE_DIR']}/node_modules/expo" : "expo" + + <<~SUPPORT_SCRIPT + #!/usr/bin/env bash +@@ -262,7 +263,7 @@ module Expo + + with_node \\ + --no-warnings \\ +- --eval "require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))" \\ ++ --eval "require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'#{expo_path}/package.json\')] }))(process.argv.slice(1))" \\ + generate-modules-provider #{args.join(' ')} \\ + --target "#{modules_provider_path}" \\ + --platform "apple" \\ diff --git a/patches/expo-modules-core+1.11.8.patch b/patches/expo-modules-core+1.11.8.patch new file mode 100644 index 000000000000..fe8c5ed7b9cc --- /dev/null +++ b/patches/expo-modules-core+1.11.8.patch @@ -0,0 +1,16 @@ +diff --git a/node_modules/expo-modules-core/android/build.gradle b/node_modules/expo-modules-core/android/build.gradle +index 3603ffd..1599a69 100644 +--- a/node_modules/expo-modules-core/android/build.gradle ++++ b/node_modules/expo-modules-core/android/build.gradle +@@ -53,9 +53,10 @@ def isExpoModulesCoreTests = { + }.call() + + def REACT_NATIVE_BUILD_FROM_SOURCE = findProject(":packages:react-native:ReactAndroid") != null ++def FALLBACK_REACT_NATIVE_DIR = hasProperty("reactNativeProject") ? file('../../react-native') : new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).parent + def REACT_NATIVE_DIR = REACT_NATIVE_BUILD_FROM_SOURCE + ? findProject(":packages:react-native:ReactAndroid").getProjectDir().parent +- : new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).parent ++ : FALLBACK_REACT_NATIVE_DIR + + def reactProperties = new Properties() + file("$REACT_NATIVE_DIR/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) } diff --git a/patches/react-native-reanimated+3.6.1.patch b/patches/react-native-reanimated+3.6.1.patch new file mode 100644 index 000000000000..3b40360d5860 --- /dev/null +++ b/patches/react-native-reanimated+3.6.1.patch @@ -0,0 +1,17 @@ +diff --git a/node_modules/react-native-reanimated/scripts/reanimated_utils.rb b/node_modules/react-native-reanimated/scripts/reanimated_utils.rb +index af0935f..ccd2a9e 100644 +--- a/node_modules/react-native-reanimated/scripts/reanimated_utils.rb ++++ b/node_modules/react-native-reanimated/scripts/reanimated_utils.rb +@@ -17,7 +17,11 @@ def find_config() + :react_native_common_dir => nil, + } + +- react_native_node_modules_dir = File.join(File.dirname(`cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('react-native/package.json')"`), '..') ++ root_project = Pod::Config.instance.installation_root.to_s ++ if(ENV['PROJECT_ROOT_DIR']) ++ root_project = ENV['PROJECT_ROOT_DIR'] ++ end ++ react_native_node_modules_dir = File.join(File.dirname(`cd "#{root_project}" && node --print "require.resolve('react-native/package.json')"`), '..') + react_native_json = try_to_parse_react_native_package_json(react_native_node_modules_dir) + + if react_native_json == nil diff --git a/patches/react-native-vision-camera+2.16.2+001+fix-boost-dependency.patch b/patches/react-native-vision-camera+2.16.5+001+fix-boost-dependency.patch similarity index 56% rename from patches/react-native-vision-camera+2.16.2+001+fix-boost-dependency.patch rename to patches/react-native-vision-camera+2.16.5+001+fix-boost-dependency.patch index ef4fbf1d5084..3afc4573985d 100644 --- a/patches/react-native-vision-camera+2.16.2+001+fix-boost-dependency.patch +++ b/patches/react-native-vision-camera+2.16.5+001+fix-boost-dependency.patch @@ -1,8 +1,17 @@ diff --git a/node_modules/react-native-vision-camera/android/build.gradle b/node_modules/react-native-vision-camera/android/build.gradle -index d308e15..2d87d8e 100644 +index ddfa243..bafffc3 100644 --- a/node_modules/react-native-vision-camera/android/build.gradle +++ b/node_modules/react-native-vision-camera/android/build.gradle -@@ -347,7 +347,7 @@ if (ENABLE_FRAME_PROCESSORS) { +@@ -334,7 +334,7 @@ if (ENABLE_FRAME_PROCESSORS) { + def thirdPartyVersions = new Properties() + thirdPartyVersions.load(new FileInputStream(thirdPartyVersionsFile)) + +- def BOOST_VERSION = thirdPartyVersions["BOOST_VERSION"] ++ def BOOST_VERSION = thirdPartyVersions["BOOST_VERSION"] ?: "1.83.0" + def boost_file = new File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz") + def DOUBLE_CONVERSION_VERSION = thirdPartyVersions["DOUBLE_CONVERSION_VERSION"] + def double_conversion_file = new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz") +@@ -352,7 +352,7 @@ if (ENABLE_FRAME_PROCESSORS) { task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { def transformedVersion = BOOST_VERSION.replace("_", ".") diff --git a/patches/react-native-vision-camera+2.16.2.patch b/patches/react-native-vision-camera+2.16.5.patch similarity index 100% rename from patches/react-native-vision-camera+2.16.2.patch rename to patches/react-native-vision-camera+2.16.5.patch diff --git a/src/App.js b/src/App.js index 8045f4eb30ad..b750d12e8c28 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,5 @@ import {PortalProvider} from '@gorhom/portal'; +import PropTypes from 'prop-types'; import React from 'react'; import {LogBox} from 'react-native'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; @@ -29,8 +30,18 @@ import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import * as Session from './libs/actions/Session'; import * as Environment from './libs/Environment/Environment'; +import InitialUrlContext from './libs/InitialUrlContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; +const propTypes = { + /** Initial url that may be passed as deeplink from Hybrid App */ + url: PropTypes.string, +}; + +const defaultProps = { + url: undefined, +}; + // For easier debugging and development, when we are in web we expose Onyx to the window, so you can more easily set data into Onyx if (window && Environment.isDevelopment()) { window.Onyx = Onyx; @@ -46,44 +57,48 @@ LogBox.ignoreLogs([ const fill = {flex: 1}; -function App() { +function App({url}) { useDefaultDragAndDrop(); OnyxUpdateManager(); return ( - - - - - - - - - - + + + + + + + + + + + + ); } +App.propTypes = propTypes; +App.defaultProps = defaultProps; App.displayName = 'App'; export default App; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 016e4267803b..e987c5b94d7d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -493,7 +493,16 @@ const ROUTES = { PROCESS_MONEY_REQUEST_HOLD: 'hold-request-educational', } as const; -export {getUrlWithBackToParam}; +/** + * Proxy routes can be used to generate a correct url with dynamic values + * + * It will be used by HybridApp, that has no access to methods generating dynamic routes in NewDot + */ +const HYBRID_APP_ROUTES = { + MONEY_REQUEST_CREATE: '/request/new/scan', +} as const; + +export {getUrlWithBackToParam, HYBRID_APP_ROUTES}; export default ROUTES; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -513,4 +522,6 @@ type RouteIsPlainString = IsEqual; */ type Route = RouteIsPlainString extends true ? never : AllRoutes; -export type {Route}; +type HybridAppRoute = (typeof HYBRID_APP_ROUTES)[keyof typeof HYBRID_APP_ROUTES]; + +export type {Route, HybridAppRoute}; diff --git a/src/languages/en.ts b/src/languages/en.ts index 860c38358b89..fd5d7d0e7b78 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -818,6 +818,7 @@ export default { phrase3: 'and', phrase4: 'Privacy', }, + returnToClassic: 'Switch to Expensify Classic', help: 'Help', accountSettings: 'Account Settings', account: 'Account', diff --git a/src/languages/es.ts b/src/languages/es.ts index 0dedf3dcdd34..3675d4b25eab 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -812,6 +812,7 @@ export default { phrase3: 'y', phrase4: 'Privacidad', }, + returnToClassic: 'Volver a Expensify Clásico', help: 'Ayuda', accountSettings: 'Configuración de la cuenta', account: 'Cuenta', diff --git a/src/libs/InitialUrlContext/index.ts b/src/libs/InitialUrlContext/index.ts new file mode 100644 index 000000000000..a87417fe4cc6 --- /dev/null +++ b/src/libs/InitialUrlContext/index.ts @@ -0,0 +1,7 @@ +import {createContext} from 'react'; +import type {Route} from '@src/ROUTES'; + +/** Initial url that will be opened when NewDot is embedded into Hybrid App. */ +const InitialUrlContext = createContext(undefined); + +export default InitialUrlContext; diff --git a/src/libs/Navigation/AppNavigator/index.tsx b/src/libs/Navigation/AppNavigator/index.tsx index 8d65f5166060..24f0f42c5d64 100644 --- a/src/libs/Navigation/AppNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/index.tsx @@ -1,4 +1,7 @@ -import React from 'react'; +import React, {useContext, useEffect} from 'react'; +import {NativeModules} from 'react-native'; +import InitialUrlContext from '@libs/InitialUrlContext'; +import Navigation from '@libs/Navigation/Navigation'; type AppNavigatorProps = { /** If we have an authToken this is true */ @@ -6,6 +9,18 @@ type AppNavigatorProps = { }; function AppNavigator({authenticated}: AppNavigatorProps) { + const initUrl = useContext(InitialUrlContext); + + useEffect(() => { + if (!NativeModules.HybridAppModule || !initUrl) { + return; + } + + Navigation.isNavigationReady().then(() => { + Navigation.navigate(initUrl); + }); + }, [initUrl]); + if (authenticated) { const AuthScreens = require('./AuthScreens').default; diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 188d8b5f337f..8009c963ade7 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -2,11 +2,12 @@ import {findFocusedRoute} from '@react-navigation/core'; import type {EventArg, NavigationContainerEventMap} from '@react-navigation/native'; import {CommonActions, getPathFromState, StackActions} from '@react-navigation/native'; import Log from '@libs/Log'; +import * as ReportUtils from '@libs/ReportUtils'; import {getReport} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; -import type {Route} from '@src/ROUTES'; -import ROUTES from '@src/ROUTES'; +import type {HybridAppRoute, Route} from '@src/ROUTES'; +import ROUTES, {HYBRID_APP_ROUTES} from '@src/ROUTES'; import {PROTECTED_SCREENS} from '@src/SCREENS'; import type {Report} from '@src/types/onyx'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; @@ -87,6 +88,18 @@ function getActiveRouteIndex(stateOrRoute: StateOrRoute, index?: number): number return index; } +/** + * Function that generates dynamic urls from paths passed from OldDot + */ +function parseHybridAppUrl(url: HybridAppRoute | Route): Route { + switch (url) { + case HYBRID_APP_ROUTES.MONEY_REQUEST_CREATE: + return ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID()); + default: + return url; + } +} + /** * Gets distance from the path in root navigator. In other words how much screen you have to pop to get to the route with this path. * The search is limited to 5 screens from the top for performance reasons. @@ -350,6 +363,7 @@ export default { getRouteNameFromStateEvent, getTopmostReportActionId, waitForProtectedRoutes, + parseHybridAppUrl, closeFullScreen, navigateWithSwitchPolicyID, }; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 48ef69f27768..d544c2ffa3b6 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -13,7 +13,7 @@ import type { import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type NAVIGATORS from '@src/NAVIGATORS'; -import type {Route as Routes} from '@src/ROUTES'; +import type {HybridAppRoute, Route as Routes} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type NavigationRef = NavigationContainerRefWithCurrent; @@ -415,7 +415,7 @@ type PublicScreensParamList = { error?: string; shortLivedAuthToken?: string; shortLivedToken?: string; - exitTo?: Routes; + exitTo?: Routes | HybridAppRoute; }; [SCREENS.VALIDATE_LOGIN]: { accountID: string; diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.tsx b/src/pages/LogInWithShortLivedAuthTokenPage.tsx index 811c35fff34e..4e7372f10dc6 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.tsx +++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useEffect} from 'react'; -import {View} from 'react-native'; +import {NativeModules, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; @@ -51,7 +51,8 @@ function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedA if (exitTo) { Navigation.isNavigationReady().then(() => { - Navigation.navigate(exitTo); + const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; + Navigation.navigate(url); }); } // The only dependencies of the effect are based on props.route diff --git a/src/pages/LogOutPreviousUserPage.js b/src/pages/LogOutPreviousUserPage.js index 974823f489ed..0d1bf4f47025 100644 --- a/src/pages/LogOutPreviousUserPage.js +++ b/src/pages/LogOutPreviousUserPage.js @@ -1,13 +1,17 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useEffect} from 'react'; -import {Linking} from 'react-native'; +import React, {useContext, useEffect} from 'react'; +import {Linking, NativeModules} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import InitialUrlContext from '@libs/InitialUrlContext'; import Log from '@libs/Log'; +import Navigation from '@libs/Navigation/Navigation'; import * as SessionUtils from '@libs/SessionUtils'; import * as Session from '@userActions/Session'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; const propTypes = { /** The details about the account that the user is signing in with */ @@ -37,10 +41,12 @@ const defaultProps = { // // This component should not do any other navigation as that handled in App.setUpPoliciesAndNavigate function LogOutPreviousUserPage(props) { + const initUrl = useContext(InitialUrlContext); useEffect(() => { - Linking.getInitialURL().then((transitionURL) => { + Linking.getInitialURL().then((url) => { const sessionEmail = props.session.email; - const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL, sessionEmail); + const transitionUrl = NativeModules.HybridAppModule ? CONST.DEEPLINK_BASE_URL + initUrl : url; + const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionUrl, sessionEmail); if (isLoggingInAsNewUser) { Session.signOutAndRedirectToSignIn(); @@ -57,11 +63,21 @@ function LogOutPreviousUserPage(props) { const shortLivedAuthToken = lodashGet(props, 'route.params.shortLivedAuthToken', ''); Session.signInWithShortLivedAuthToken(email, shortLivedAuthToken); } - }); - // We only want to run this effect once on mount (when the page first loads after transitioning from OldDot) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const exitTo = lodashGet(props, 'route.params.exitTo', ''); + // We don't want to navigate to the exitTo route when creating a new workspace from a deep link, + // because we already handle creating the optimistic policy and navigating to it in App.setUpPoliciesAndNavigate, + // which is already called when AuthScreens mounts. + if (exitTo && exitTo !== ROUTES.WORKSPACE_NEW && !props.account.isLoading && !isLoggingInAsNewUser) { + Navigation.isNavigationReady().then(() => { + // remove this screen and navigate to exit route + const exitUrl = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; + Navigation.goBack(); + Navigation.navigate(exitUrl); + }); + } + }); + }, [initUrl, props]); return ; } diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 6f547564e7a9..5b85359d5018 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -2,7 +2,7 @@ import {useNavigationState} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {View} from 'react-native'; +import {NativeModules, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; @@ -131,7 +131,7 @@ function InitialSettingsPage(props) { const profileBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(props.loginList); const paymentCardList = props.fundList || {}; - return { + const defaultMenu = { sectionStyle: styles.accountSettingsSectionContainer, sectionTranslationKey: 'initialSettingsPage.account', items: [ @@ -182,6 +182,26 @@ function InitialSettingsPage(props) { }, ], }; + + if (NativeModules.HybridAppModule) { + const hybridAppMenuItems = _.filter( + [ + { + translationKey: 'initialSettingsPage.returnToClassic', + icon: Expensicons.RotateLeft, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + action: () => NativeModules.HybridAppModule.closeReactNativeApp(), + }, + ...defaultMenu.items, + ], + (item) => item.translationKey !== 'initialSettingsPage.signOut' && item.translationKey !== 'initialSettingsPage.goToExpensifyClassic', + ); + + return {sectionStyle: styles.accountSettingsSectionContainer, sectionTranslationKey: 'initialSettingsPage.account', items: hybridAppMenuItems}; + } + + return defaultMenu; }, [props.bankAccountList, props.fundList, props.loginList, props.userWallet.errors, props.walletTerms.errors, signOut, styles.accountSettingsSectionContainer]); /**