diff --git a/lib/android/app/build.gradle b/lib/android/app/build.gradle index 669e84e2024..3f348f7e537 100644 --- a/lib/android/app/build.gradle +++ b/lib/android/app/build.gradle @@ -3,6 +3,13 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: "com.facebook.react" + + +react { + codegenJavaPackageName = "com.reactnativenavigation.react" +} + def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback @@ -193,7 +200,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesCore" implementation "androidx.constraintlayout:constraintlayout:2.0.4" - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.annotation:annotation:1.2.0' implementation 'com.google.android.material:material:1.2.0-alpha03' diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationPackage.kt b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationPackage.kt index 2796d689227..5702dbf1c30 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationPackage.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationPackage.kt @@ -1,27 +1,41 @@ package com.reactnativenavigation.react +import com.facebook.react.BaseReactPackage import com.facebook.react.ReactApplication -import com.facebook.react.ReactHost -import com.facebook.react.ReactPackage 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 com.facebook.react.uimanager.ViewManager import com.reactnativenavigation.options.LayoutFactory import com.reactnativenavigation.react.modal.ModalViewManager -class NavigationPackage() : ReactPackage { - override fun createNativeModules(reactContext: ReactApplicationContext): List { - val reactApp = reactContext.applicationContext as ReactApplication - return listOf( - NavigationModule( - reactContext, - LayoutFactory(reactApp.reactHost) - ) - ) +class NavigationPackage() : BaseReactPackage() { + + override fun getModule(name: String, context: ReactApplicationContext): NativeModule? { + val reactApp = context.applicationContext as ReactApplication + return when (name) { + NavigationTurboModule.NAME -> { + NavigationTurboModule(context, LayoutFactory(reactApp.reactHost)) + } + else -> { + null + } + } } - override fun createViewManagers(reactContext: ReactApplicationContext): List> { + override fun getReactModuleInfoProvider() = ReactModuleInfoProvider { + mapOf(NavigationTurboModule.NAME to ReactModuleInfo( + _name = NavigationTurboModule.NAME, + _className = NavigationTurboModule.NAME, + _canOverrideExistingModule = false, + _needsEagerInit = false, + isCxxModule = false, + isTurboModule = true + )) + } - return listOf(ModalViewManager(reactContext)) + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return mutableListOf(ModalViewManager(reactContext)) } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationTurboModule.kt b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationTurboModule.kt new file mode 100644 index 00000000000..ac132fb5a7a --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationTurboModule.kt @@ -0,0 +1,291 @@ +package com.reactnativenavigation.react + +import android.util.Log +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.reactnativenavigation.NavigationActivity +import com.reactnativenavigation.NavigationApplication +import com.reactnativenavigation.options.LayoutFactory +import com.reactnativenavigation.options.Options +import com.reactnativenavigation.options.parsers.JSONParser +import com.reactnativenavigation.options.parsers.LayoutNodeParser +import com.reactnativenavigation.options.parsers.TypefaceLoader +import com.reactnativenavigation.react.events.EventEmitter +import com.reactnativenavigation.utils.LaunchArgsParser +import com.reactnativenavigation.utils.Now +import com.reactnativenavigation.utils.SystemUiUtils.getStatusBarHeight +import com.reactnativenavigation.utils.UiThread +import com.reactnativenavigation.utils.UiUtils +import com.reactnativenavigation.viewcontrollers.navigator.Navigator +import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController +import java.util.Objects + +class NavigationTurboModule( + reactContext: ReactApplicationContext, + private val layoutFactory: LayoutFactory +) : NativeRNNTurboModuleSpec(reactContext) { + + private val now = Now() + private val jsonParser: JSONParser = JSONParser() + private lateinit var eventEmitter: EventEmitter + + init { + reactContext.addLifecycleEventListener(object : LifecycleEventListenerAdapter() { + override fun onHostPause() { + super.onHostPause() + UiUtils.runOnMainThread { + navigator().onHostPause() + } + } + + override fun onHostResume() { + eventEmitter = EventEmitter(reactContext) + navigator().setEventEmitter(eventEmitter) + layoutFactory.init( + activity(), + eventEmitter, + navigator().getChildRegistry(), + (activity().application as NavigationApplication).externalComponents + ) + UiUtils.runOnMainThread { navigator().onHostResume() } + } + }) + } + + override fun getTypedExportedConstants(): MutableMap { + val constants = mutableMapOf() + constants[Constants.BACK_BUTTON_JS_KEY] = Constants.BACK_BUTTON_ID + constants[Constants.BOTTOM_TABS_HEIGHT_KEY] = + UiUtils.pxToDp( + reactApplicationContext, + UiUtils.getBottomTabsHeight(reactApplicationContext).toFloat() + ).toDouble() + constants[Constants.STATUS_BAR_HEIGHT_KEY] = + UiUtils.pxToDp(reactApplicationContext, getStatusBarHeight(currentActivity).toFloat()) + .toDouble() + constants[Constants.TOP_BAR_HEIGHT_KEY] = UiUtils.pxToDp( + reactApplicationContext, + UiUtils.getTopBarHeight(reactApplicationContext).toFloat() + ).toDouble() + return constants + } + + override fun setRoot(commandId: String, layout: ReadableMap, promise: Promise) { + Log.d("NavigationTurboModule", "setRoot ${Thread.currentThread()}") + val layoutTree = LayoutNodeParser.parse( + Objects.requireNonNull( + jsonParser.parse(layout).optJSONObject("root") + ) + ) + handle { + Log.d("NavigationTurboModule", "setRoot handle ${Thread.currentThread()}") + val viewController = layoutFactory.create(layoutTree) + navigator().setRoot( + viewController, + NativeCommandListener("setRoot", commandId, promise, eventEmitter, now) + ) + } + } + + override fun setDefaultOptions(options: ReadableMap?) { + handle { + val defaultOptions = parse(options) + layoutFactory.defaultOptions = defaultOptions + navigator().defaultOptions = defaultOptions + } + } + + override fun mergeOptions(componentId: String?, options: ReadableMap?) { + handle { navigator().mergeOptions(componentId, parse(options)) } + } + + override fun push( + commandId: String, + componentId: String, + layout: ReadableMap, + promise: Promise + ) { + val layoutTree = LayoutNodeParser.parse(jsonParser.parse(layout)) + handle { + val viewController = layoutFactory.create(layoutTree) + navigator().push( + componentId, + viewController, + NativeCommandListener("push", commandId, promise, eventEmitter, now) + ) + } + } + + override fun pop( + commandId: String, + componentId: String, + options: ReadableMap?, + promise: Promise + ) { + handle { + navigator().pop( + componentId, + parse(options), + NativeCommandListener("pop", commandId, promise, eventEmitter, now) + ) + } + } + + override fun popTo( + commandId: String, + componentId: String, + options: ReadableMap?, + promise: Promise + ) { + handle { + navigator().popTo( + componentId, + parse(options), + NativeCommandListener("popTo", commandId, promise, eventEmitter, now) + ) + } + } + + override fun popToRoot( + commandId: String, + componentId: String, + options: ReadableMap?, + promise: Promise + ) { + handle { + navigator().popToRoot( + componentId, + parse(options), + NativeCommandListener("popToRoot", commandId, promise, eventEmitter, now) + ) + } + } + + override fun setStackRoot( + commandId: String, + componentId: String, + children: ReadableArray, + promise: Promise + ) { + handle { + val _children = ArrayList>() + for (i in 0.. + if (p.hasProperty('android')) { + android { + buildToolsVersion rootProject.ext.buildToolsVersion + } } } } diff --git a/lib/ios/Constants.mm b/lib/ios/Constants.mm index 74bbd440354..bbc89da6590 100644 --- a/lib/ios/Constants.mm +++ b/lib/ios/Constants.mm @@ -16,7 +16,8 @@ + (NSDictionary *)getConstants { JS::NativeRNNTurboModule::Constants::Builder::Input input = { [self topBarHeight], [self statusBarHeight], - [self bottomTabsHeight] + [self bottomTabsHeight], + [self backButtonId] }; return input; @@ -35,4 +36,9 @@ + (CGFloat)bottomTabsHeight { return [UIApplication.sharedApplication.delegate.window.rootViewController getBottomTabsHeight]; } ++ (NSString*)backButtonId { + // This property is used only in android but we have to add it for compatability in turbo modules + return @""; +} + @end diff --git a/lib/src/adapters/NativeCommandsSender.ts b/lib/src/adapters/NativeCommandsSender.ts index e82f40bafca..f442e3e292d 100644 --- a/lib/src/adapters/NativeCommandsSender.ts +++ b/lib/src/adapters/NativeCommandsSender.ts @@ -1,5 +1,5 @@ import { NavigationConstants } from './Constants'; -import RNNCommandsModule from './NativeRNNTurboModule'; +import RNNCommandsModule, { Spec } from './NativeRNNTurboModule'; interface NativeCommandsModule { setRoot(commandId: string, layout: { root: any; modals: any[]; overlays: any[] }): Promise; @@ -9,7 +9,7 @@ interface NativeCommandsModule { pop(commandId: string, componentId: string, options?: object): Promise; popTo(commandId: string, componentId: string, options?: object): Promise; popToRoot(commandId: string, componentId: string, options?: object): Promise; - setStackRoot(commandId: string, onComponentId: string, layout: object): Promise; + setStackRoot(commandId: string, onComponentId: string, layout: object[]): Promise; showModal(commandId: string, layout: object): Promise; dismissModal(commandId: string, componentId: string, options?: object): Promise; dismissAllModals(commandId: string, options?: object): Promise; @@ -23,8 +23,8 @@ interface NativeCommandsModule { getConstants?: () => NavigationConstants; } -export class NativeCommandsSender { - private readonly nativeCommandsModule: NativeCommandsModule; +export class NativeCommandsSender implements NativeCommandsModule { + private readonly nativeCommandsModule: Spec; constructor() { this.nativeCommandsModule = RNNCommandsModule; } @@ -57,7 +57,7 @@ export class NativeCommandsSender { return this.nativeCommandsModule.popToRoot(commandId, componentId, options); } - setStackRoot(commandId: string, onComponentId: string, layout: object) { + setStackRoot(commandId: string, onComponentId: string, layout: object[]) { return this.nativeCommandsModule.setStackRoot(commandId, onComponentId, layout); } @@ -90,10 +90,10 @@ export class NativeCommandsSender { } getNavigationConstants() { - return this.nativeCommandsModule.getNavigationConstants(); + return Promise.resolve(this.nativeCommandsModule.getConstants()); } getNavigationConstantsSync() { - return this.nativeCommandsModule.getNavigationConstantsSync(); + return this.nativeCommandsModule.getConstants(); } } diff --git a/lib/src/adapters/NativeRNNTurboModule.ts b/lib/src/adapters/NativeRNNTurboModule.ts index 808a9d71faa..1942edc2239 100644 --- a/lib/src/adapters/NativeRNNTurboModule.ts +++ b/lib/src/adapters/NativeRNNTurboModule.ts @@ -1,4 +1,4 @@ -import { TurboModule, TurboModuleRegistry, NativeModules, Platform } from 'react-native'; +import { TurboModule, TurboModuleRegistry } from 'react-native'; import { UnsafeObject, Double } from 'react-native/Libraries/Types/CodegenTypes'; export interface Spec extends TurboModule { @@ -6,42 +6,44 @@ export interface Spec extends TurboModule { topBarHeight: Double; statusBarHeight: Double; bottomTabsHeight: Double; + backButtonId: string; }; setRoot(commandId: string, layout: UnsafeObject): Promise; + setDefaultOptions(options: UnsafeObject): void; + mergeOptions(componentId: string, options: UnsafeObject): void; + push(commandId: string, componentId: string, layout: UnsafeObject): Promise; - pop(commandId: string, componentId: string, options: UnsafeObject): Promise; - popTo(commandId: string, componentId: string, options: UnsafeObject): Promise; - popToRoot(commandId: string, componentId: string, options: UnsafeObject): Promise; + + pop(commandId: string, componentId: string, options?: UnsafeObject): Promise; + + popTo(commandId: string, componentId: string, options?: UnsafeObject): Promise; + + popToRoot(commandId: string, componentId: string, options?: UnsafeObject): Promise; + setStackRoot( commandId: string, componentId: string, layout: Array ): Promise; + showModal(commandId: string, layout: UnsafeObject): Promise; - dismissModal(commandId: string, componentId: string, options: UnsafeObject): Promise; - dismissAllModals(commandId: string, options: UnsafeObject): Promise; + + dismissModal(commandId: string, componentId: string, options?: UnsafeObject): Promise; + + dismissAllModals(commandId: string, options?: UnsafeObject): Promise; + showOverlay(commandId: string, layout: UnsafeObject): Promise; - dismissOverlay(commandId: string, componentId: string): Promise; - dismissAllOverlays(commandId: string): Promise; - getLaunchArgs(commandId: string): Promise>; -} -const isTurboModuleEnabled = (globalThis as any).__turboModuleProxy != null; + dismissOverlay(commandId: string, componentId: string): Promise; -const commands = - isTurboModuleEnabled && Platform.OS === 'ios' - ? TurboModuleRegistry.get('RNNTurboModule') - : NativeModules.RNNBridgeModule; + dismissAllOverlays(commandId: string): Promise; -const isNewArchWithBridgeless = isTurboModuleEnabled; + getLaunchArgs(commandId: string): Promise>; +} -export const RCTAssertNewArchEnabled = () => { - if (!isNewArchWithBridgeless) - throw new Error('Allowed only in New Architecture with Bridgeless!'); -}; +const commands = TurboModuleRegistry.get('RNNTurboModule')!; -export const isRNNTurboModuleAvailable = isNewArchWithBridgeless; export default commands; diff --git a/lib/src/commands/Commands.test.ts b/lib/src/commands/Commands.test.ts index 3f8c3febea7..f03b9d87560 100644 --- a/lib/src/commands/Commands.test.ts +++ b/lib/src/commands/Commands.test.ts @@ -634,10 +634,10 @@ describe('Commands', () => { describe('dismissOverlay', () => { it('check promise returns true', async () => { - when(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).thenResolve(true); + when(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).thenResolve('true'); const result = await uut.dismissOverlay('Component1'); verify(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).called(); - expect(result).toEqual(true); + expect(result).toEqual('true'); }); it('send command to native with componentId', () => { diff --git a/package-lock.json b/package-lock.json index a404aa05ac0..bdebe3125ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-native-navigation", - "version": "8.0.0", + "version": "8.1.0-alpha", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-native-navigation", - "version": "8.0.0", + "version": "8.1.0-alpha", "license": "MIT", "dependencies": { "hoist-non-react-statics": "3.x.x", diff --git a/package.json b/package.json index 988b23c10de..9d403179e69 100644 --- a/package.json +++ b/package.json @@ -258,9 +258,6 @@ "codegenConfig": { "name": "rnnavigation", "type": "all", - "jsSrcsDir": "./lib/src", - "android": { - "javaPackageName": "wix.rnn.specs" - } + "jsSrcsDir": "./lib/src" } -} \ No newline at end of file +} diff --git a/playground/android/app/build.gradle b/playground/android/app/build.gradle index 0382536bb1b..e950fed9345 100644 --- a/playground/android/app/build.gradle +++ b/playground/android/app/build.gradle @@ -5,6 +5,7 @@ react { root = file("../../../") reactNativeDir = file("../../../node_modules/react-native") codegenDir = file("../../../node_modules/@react-native/codegen") + codegenJavaPackageName = "com.reactnativenavigation.playground" autolinkLibrariesWithApp() }