Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6406e0d
wip
war-in Feb 19, 2025
c128b40
native part
war-in Feb 25, 2025
9e744e5
remove HybridAppModules
war-in Feb 25, 2025
56b171b
refactor ts part
war-in Feb 25, 2025
c19b951
simplify HybridAppModule
war-in Feb 25, 2025
73d1b3a
Merge branch 'main' into war-in/hybrid-app-turbomodule
war-in Feb 25, 2025
ef370cc
post-merge fixes
war-in Feb 25, 2025
91a78a2
fix prettier
war-in Feb 25, 2025
70223f9
fix lint
war-in Feb 25, 2025
ad70f0c
fix lint
war-in Feb 25, 2025
2860064
fix tests
war-in Feb 25, 2025
4b8151d
fix rest of the tests
war-in Feb 25, 2025
7aebfe4
Merge branch 'main' into war-in/hybrid-app-turbomodule
war-in Feb 26, 2025
f3dbfdf
apply review comments
war-in Feb 26, 2025
4803b5f
add IS_HYBRID_APP to config
war-in Feb 26, 2025
093d286
Merge branch 'main' into war-in/hybrid-app-turbomodule
war-in Feb 28, 2025
9759090
revert submodule update
war-in Feb 28, 2025
75c6411
move setRootStatusBarEnabled to correct place
war-in Feb 28, 2025
2e52297
fix broken web
war-in Feb 28, 2025
537ba4e
Merge branch 'main' into war-in/hybrid-app-turbomodule
war-in Mar 4, 2025
b46ff0a
apply review suggestions
war-in Mar 4, 2025
3bb959f
fix lint
war-in Mar 4, 2025
04141ef
Merge branch 'main' into war-in/hybrid-app-turbomodule
war-in Mar 6, 2025
596a5a1
improve HybridAppModule typing
war-in Mar 6, 2025
6282740
fix prettier
war-in Mar 6, 2025
bed157d
don't create custom name for ReactNativeHybridAppModule
war-in Mar 6, 2025
9a074dd
revert previous commit
war-in Mar 6, 2025
0871db8
rename turbomodule and replace package if in HybridApp
war-in Mar 6, 2025
6054969
Merge branch 'main' into war-in/hybrid-app-turbomodule
war-in Mar 10, 2025
3a996ad
fix prettier
war-in Mar 10, 2025
d293e80
Merge branch 'main' into war-in/hybrid-app-turbomodule
war-in Mar 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,27 @@ PODS:
- React-logger (= 0.76.3)
- React-perflogger (= 0.76.3)
- React-utils (= 0.76.3)
- ReactNativeHybridApp (0.0.0):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNAppleAuthentication (2.2.2):
- React-Core
- RNCClipboard (1.15.0):
Expand Down Expand Up @@ -2963,6 +2984,7 @@ DEPENDENCIES:
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactCodegen (from `build/generated/ios`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "ReactNativeHybridApp (from `../node_modules/@expensify/react-native-hybrid-app`)"
- "RNAppleAuthentication (from `../node_modules/@invertase/react-native-apple-authentication`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- "RNCPicker (from `../node_modules/@react-native-picker/picker`)"
Expand Down Expand Up @@ -3233,6 +3255,8 @@ EXTERNAL SOURCES:
:path: build/generated/ios
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
ReactNativeHybridApp:
:path: "../node_modules/@expensify/react-native-hybrid-app"
RNAppleAuthentication:
:path: "../node_modules/@invertase/react-native-apple-authentication"
RNCClipboard:
Expand Down Expand Up @@ -3421,6 +3445,7 @@ SPEC CHECKSUMS:
React-utils: 2bcaf4f4dfe361344bce2fae428603d518488630
ReactCodegen: ae99a130606068ed40d1d9c0d5f25fda142a0647
ReactCommon: 89c87b343deacc8610b099ac764848f0ce937e3e
ReactNativeHybridApp: 18cab0d029f08d51391a07c5c2c7872287544485
RNAppleAuthentication: 0571c08da8c327ae2afc0261b48b4a515b0286a6
RNCClipboard: 8212ca9e8370d0e23bfb7f5a591380da5a63456d
RNCPicker: b978067931744f5a7316b48b8dcf145d4d722672
Expand Down
7 changes: 7 additions & 0 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ jest.mock('../modules/background-task/src/NativeReactNativeBackgroundTask', () =
onBackgroundTaskExecution: jest.fn(),
}));

jest.mock('../modules/hybrid-app/src/NativeReactNativeHybridApp', () => ({
isHybridApp: jest.fn(),
closeReactNativeApp: jest.fn(),
completeOnboarding: jest.fn(),
switchAccount: jest.fn(),
}));

// This makes FlatList render synchronously for easier testing.
jest.mock(
'@react-native/virtualized-lists/Interaction/Batchinator',
Expand Down
22 changes: 22 additions & 0 deletions modules/hybrid-app/ReactNativeHybridApp.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
s.name = "ReactNativeHybridApp"
s.version = package["version"]
s.summary = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.authors = package["author"]

s.platforms = { :ios => min_ios_version_supported }
s.source = { :git => ".git", :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,cpp}"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
}

install_modules_dependencies(s)
end
82 changes: 82 additions & 0 deletions modules/hybrid-app/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
buildscript {
ext.getExtOrDefault = { name ->
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeHybridApp_' + name]
}

repositories {
google()
mavenCentral()
}

dependencies {
classpath "com.android.tools.build:gradle:8.7.2"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
}
}

apply plugin: "com.android.library"
apply plugin: "kotlin-android"
apply plugin: "com.facebook.react"

def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ReactNativeHybridApp_" + name]).toInteger()
}

android {
namespace "com.expensify.reactnativehybridapp"

compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")

defaultConfig {
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
}

buildFeatures {
buildConfig true
}

buildTypes {
release {
minifyEnabled false
}
}

lintOptions {
disable "GradleCompatible"
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

sourceSets {
main {
java.srcDirs += [
"generated/java",
"generated/jni"
]
}
}
}

repositories {
mavenCentral()
google()
}

def kotlin_version = getExtOrDefault("kotlinVersion")

dependencies {
implementation "com.facebook.react:react-android"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

react {
jsRootDir = file("../src/")
libraryName = "ReactNativeHybridApp"
codegenJavaPackageName = "com.expensify.reactnativehybridapp"
}
5 changes: 5 additions & 0 deletions modules/hybrid-app/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ReactNativeHybridApp_kotlinVersion=2.0.21
ReactNativeHybridApp_minSdkVersion=24
ReactNativeHybridApp_targetSdkVersion=34
ReactNativeHybridApp_compileSdkVersion=35
ReactNativeHybridApp_ndkVersion=27.1.12297006
3 changes: 3 additions & 0 deletions modules/hybrid-app/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
Comment thread
war-in marked this conversation as resolved.
package="com.expensify.reactnativehybridapp">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.expensify.reactnativehybridapp

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule
import android.util.Log

@ReactModule(name = NativeReactNativeHybridAppSpec.NAME)
class ReactNativeHybridApp(reactContext: ReactApplicationContext) :
NativeReactNativeHybridAppSpec(reactContext) {

override fun isHybridApp(): Boolean {
return false
}

override fun closeReactNativeApp(shouldSignOut: Boolean, shouldSetNVP: Boolean) {
Log.d(NAME, "`closeReactNativeApp` should never be called in standalone `New Expensify` app")
}

override fun completeOnboarding(status: Boolean) {
Log.d(NAME, "`completeOnboarding` should never be called in standalone `New Expensify` app")
}

override fun switchAccount(
newDotCurrentAccountEmail: String?,
authToken: String?,
policyID: String?,
accountID: String?
) {
Log.d(NAME, "`switchAccount` should never be called in standalone `New Expensify` app")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.expensify.reactnativehybridapp

import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import java.util.HashMap

class ReactNativeHybridAppPackage : BaseReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return if (name == NativeReactNativeHybridAppSpec.NAME) {
ReactNativeHybridApp(reactContext)
} else {
null
}
}

override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
return ReactModuleInfoProvider {
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
moduleInfos[NativeReactNativeHybridAppSpec.NAME] = ReactModuleInfo(
NativeReactNativeHybridAppSpec.NAME,
NativeReactNativeHybridAppSpec.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCxxModule
true // isTurboModule
)
moduleInfos
}
}
}
6 changes: 6 additions & 0 deletions modules/hybrid-app/ios/ReactNativeHybridApp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

#import "RNReactNativeHybridAppSpec/RNReactNativeHybridAppSpec.h"

@interface ReactNativeHybridApp : NSObject <NativeReactNativeHybridAppSpec>

@end
29 changes: 29 additions & 0 deletions modules/hybrid-app/ios/ReactNativeHybridApp.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#import "ReactNativeHybridApp.h"

@implementation ReactNativeHybridApp
RCT_EXPORT_MODULE()

- (NSNumber *)isHybridApp {
return @false;
}

- (void)closeReactNativeApp:(BOOL)shouldSignOut shouldSetNVP:(BOOL)shouldSetNVP {
NSLog(@"[ReactNativeHybridApp] `closeReactNativeApp` should never be called in standalone `New Expensify` app");
}

- (void)completeOnboarding:(BOOL)status {
NSLog(@"[ReactNativeHybridApp] `completeOnboarding` should never be called in standalone `New Expensify` app");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

}

- (void)switchAccount:(NSString *)newDotCurrentAccountEmail authToken:(NSString *)authToken policyID:(NSString *)policyID accountID:(NSString *)accountID {
NSLog(@"[ReactNativeHybridApp] `switchAccount` should never be called in standalone `New Expensify` app");
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeReactNativeHybridAppSpecJSI>(params);
}

@end
19 changes: 19 additions & 0 deletions modules/hybrid-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@expensify/react-native-hybrid-app",
"version": "0.0.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely NAB, but maybe we could increment the version 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd keep it that way because we have 0.0.0 in background-task too

"description": "HybridApp",
"main": "src/index",
"codegenConfig": {
"name": "RNReactNativeHybridAppSpec",
"type": "modules",
"jsSrcsDir": "src"
},
"author": " <> ()",
Comment thread
war-in marked this conversation as resolved.
"license": "UNLICENSED",
"homepage": "#readme",
"create-react-native-library": {
"type": "turbo-module",
"languages": "kotlin-objc",
"version": "0.48.1"
}
}
12 changes: 12 additions & 0 deletions modules/hybrid-app/react-native.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @type {import('@react-native-community/cli-types').UserDependencyConfig}
*/
module.exports = {
dependency: {
platforms: {
android: {
cmakeListsPath: 'build/generated/source/codegen/jni/CMakeLists.txt',
},
},
},
};
12 changes: 12 additions & 0 deletions modules/hybrid-app/src/NativeReactNativeHybridApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

// eslint-disable-next-line rulesdir/no-inline-named-export, @typescript-eslint/consistent-type-definitions
export interface Spec extends TurboModule {
isHybridApp: () => boolean;
Comment thread
war-in marked this conversation as resolved.
closeReactNativeApp: (shouldSignOut: boolean, shouldSetNVP: boolean) => void;
completeOnboarding: (status: boolean) => void;
switchAccount: (newDotCurrentAccountEmail: string, authToken: string, policyID: string, accountID: string) => void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('ReactNativeHybridApp');
19 changes: 19 additions & 0 deletions modules/hybrid-app/src/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import ReactNativeHybridApp from './NativeReactNativeHybridApp';
import type HybridAppModuleType from './types';

const HybridAppModule: HybridAppModuleType = {
isHybridApp() {
return ReactNativeHybridApp.isHybridApp();
},
closeReactNativeApp({shouldSignOut, shouldSetNVP}) {
ReactNativeHybridApp.closeReactNativeApp(shouldSignOut, shouldSetNVP);
},
completeOnboarding({status}) {
ReactNativeHybridApp.completeOnboarding(status);
},
switchAccount({newDotCurrentAccountEmail, authToken, policyID, accountID}) {
ReactNativeHybridApp.switchAccount(newDotCurrentAccountEmail, authToken, policyID, accountID);
},
};

export default HybridAppModule;
19 changes: 19 additions & 0 deletions modules/hybrid-app/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Log from '@libs/Log';
import type HybridAppModuleType from './types';

const HybridAppModule: HybridAppModuleType = {
isHybridApp() {
return false;
},
closeReactNativeApp() {
Log.warn('HybridAppModule: `closeReactNativeApp` should never be called on web');
},
completeOnboarding() {
Log.warn('HybridAppModule: `completeOnboarding` should never be called on web');
},
switchAccount() {
Log.warn('HybridAppModule: `switchAccount` should never be called on web');
},
};

export default HybridAppModule;
8 changes: 8 additions & 0 deletions modules/hybrid-app/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type HybridAppModuleType = {
isHybridApp: () => boolean;
closeReactNativeApp: (args: {shouldSignOut: boolean; shouldSetNVP: boolean}) => void;
completeOnboarding: (args: {status: boolean}) => void;
switchAccount: (args: {newDotCurrentAccountEmail: string; authToken: string; policyID: string; accountID: string}) => void;
};

export default HybridAppModuleType;
Loading