@@ -31,6 +31,9 @@ const scriptDir = path.dirname(scriptFile);
3131
3232// Package root is one level up from scripts/
3333const PACKAGE_ROOT = path . resolve ( scriptDir , '../../../' ) ;
34+ const DEFAULT_ANDROID_JAVA_PACKAGE = 'com.callstack.nativebrownfieldnavigation' ;
35+ const ANDROID_JAVA_PACKAGE_NAME = DEFAULT_ANDROID_JAVA_PACKAGE ;
36+ const ANDROID_JAVA_PACKAGE_PATH_SEGMENTS = ANDROID_JAVA_PACKAGE_NAME . split ( '.' ) ;
3437
3538// ============================================================================
3639// Types
@@ -69,6 +72,14 @@ const TS_TO_SWIFT_TYPE: Record<string, string> = {
6972 Object : '[String: Any]' ,
7073} ;
7174
75+ const TS_TO_KOTLIN_TYPE : Record < string , string > = {
76+ string : 'String' ,
77+ number : 'Double' ,
78+ boolean : 'Boolean' ,
79+ void : 'Unit' ,
80+ Object : 'ReadableMap' ,
81+ } ;
82+
7283function mapTsTypeToObjC ( tsType : string , nullable : boolean = false ) : string {
7384 if ( tsType . startsWith ( 'Promise<' ) ) {
7485 return 'void' ;
@@ -99,6 +110,20 @@ function mapTsTypeToSwift(tsType: string, optional: boolean = false): string {
99110 return optional ? 'Any?' : 'Any' ;
100111}
101112
113+ function mapTsTypeToKotlin ( tsType : string , optional : boolean = false ) : string {
114+ if ( tsType . startsWith ( 'Promise<' ) ) {
115+ const inner = tsType . slice ( 8 , - 1 ) ;
116+ return mapTsTypeToKotlin ( inner , optional ) ;
117+ }
118+
119+ const mapped = TS_TO_KOTLIN_TYPE [ tsType ] ;
120+ if ( mapped ) {
121+ return optional ? `${ mapped } ?` : mapped ;
122+ }
123+
124+ return optional ? 'Any?' : 'Any' ;
125+ }
126+
102127// ============================================================================
103128// Spec Parser
104129// ============================================================================
@@ -339,6 +364,108 @@ ${methodImpls}
339364` ;
340365}
341366
367+ function generateKotlinDelegate (
368+ methods : MethodSignature [ ] ,
369+ kotlinPackageName : string
370+ ) : string {
371+ const methodSignatures = methods
372+ . map ( ( m ) => {
373+ const params = m . params
374+ . map ( ( p ) => `${ p . name } : ${ mapTsTypeToKotlin ( p . type , p . optional ) } ` )
375+ . join ( ', ' ) ;
376+ const returnType =
377+ m . returnType === 'void'
378+ ? ''
379+ : `: ${ mapTsTypeToKotlin ( m . returnType , false ) } ` ;
380+ return ` fun ${ m . name } (${ params } )${ returnType } ` ;
381+ } )
382+ . join ( '\n' ) ;
383+
384+ return `package ${ kotlinPackageName }
385+
386+ interface BrownfieldNavigationDelegate {
387+ ${ methodSignatures }
388+ }
389+ ` ;
390+ }
391+
392+ function generateKotlinModule (
393+ methods : MethodSignature [ ] ,
394+ kotlinPackageName : string
395+ ) : string {
396+ const hasAsyncMethod = methods . some ( ( m ) => m . isAsync ) ;
397+ const hasObjectType = methods . some (
398+ ( m ) =>
399+ m . returnType . includes ( 'Object' ) ||
400+ m . params . some ( ( p ) => p . type === 'Object' )
401+ ) ;
402+
403+ const methodImpls = methods
404+ . map ( ( m ) => {
405+ if ( m . isAsync ) {
406+ return generateAsyncKotlinMethod ( m ) ;
407+ }
408+ return generateSyncKotlinMethod ( m ) ;
409+ } )
410+ . join ( '\n\n' ) ;
411+
412+ return `package ${ kotlinPackageName }
413+
414+ import com.facebook.react.bridge.ReactApplicationContext
415+ import com.facebook.react.bridge.ReactMethod${
416+ hasAsyncMethod ? '\nimport com.facebook.react.bridge.Promise' : ''
417+ } ${ hasObjectType ? '\nimport com.facebook.react.bridge.ReadableMap' : '' }
418+
419+ class NativeBrownfieldNavigationModule(
420+ reactContext: ReactApplicationContext
421+ ) : NativeBrownfieldNavigationSpec(reactContext) {
422+ ${ methodImpls }
423+
424+ companion object {
425+ const val NAME = "NativeBrownfieldNavigation"
426+ }
427+ }
428+ ` ;
429+ }
430+
431+ function generateSyncKotlinMethod ( method : MethodSignature ) : string {
432+ const params = method . params
433+ . map ( ( p ) => `${ p . name } : ${ mapTsTypeToKotlin ( p . type , p . optional ) } ` )
434+ . join ( ', ' ) ;
435+ const args = method . params . map ( ( p ) => p . name ) . join ( ', ' ) ;
436+
437+ const signature = ` @ReactMethod\n override fun ${ method . name } (${ params } )${
438+ method . returnType === 'void'
439+ ? ''
440+ : `: ${ mapTsTypeToKotlin ( method . returnType , false ) } `
441+ } `;
442+
443+ if ( method . returnType === 'void' ) {
444+ return `${ signature } {
445+ BrownfieldNavigationManager.getDelegate().${ method . name } (${ args } )
446+ }` ;
447+ }
448+
449+ return `${ signature } {
450+ return BrownfieldNavigationManager.getDelegate().${ method . name } (${ args } )
451+ }` ;
452+ }
453+
454+ function generateAsyncKotlinMethod ( method : MethodSignature ) : string {
455+ const paramsWithTypes = method . params
456+ . map ( ( p ) => `${ p . name } : ${ mapTsTypeToKotlin ( p . type , p . optional ) } ` )
457+ . join ( ', ' ) ;
458+ const params =
459+ paramsWithTypes . length > 0
460+ ? `${ paramsWithTypes } , promise: Promise`
461+ : 'promise: Promise' ;
462+
463+ return ` @ReactMethod
464+ override fun ${ method . name } (${ params } ) {
465+ promise.reject("not_implemented", "${ method . name } is not implemented")
466+ }` ;
467+ }
468+
342469function generateSyncObjCMethod ( method : MethodSignature ) : string {
343470 const { name, params, returnType } = method ;
344471
@@ -477,6 +604,11 @@ function main(): void {
477604 const indexDts = generateIndexDts ( methods ) ;
478605 const swiftDelegate = generateSwiftDelegate ( methods ) ;
479606 const objcImpl = generateObjCImplementation ( methods ) ;
607+ const kotlinDelegate = generateKotlinDelegate (
608+ methods ,
609+ ANDROID_JAVA_PACKAGE_NAME
610+ ) ;
611+ const kotlinModule = generateKotlinModule ( methods , ANDROID_JAVA_PACKAGE_NAME ) ;
480612
481613 if ( dryRun ) {
482614 console . log ( '\n--- Generated: src/NativeBrownfieldNavigation.ts ---' ) ;
@@ -493,6 +625,14 @@ function main(): void {
493625 console . log ( swiftDelegate ) ;
494626 console . log ( '\n--- Generated: ios/NativeBrownfieldNavigation.mm ---' ) ;
495627 console . log ( objcImpl ) ;
628+ console . log (
629+ `\n--- Generated: android/src/main/java/${ ANDROID_JAVA_PACKAGE_NAME . replaceAll ( '.' , '/' ) } /BrownfieldNavigationDelegate.kt ---`
630+ ) ;
631+ console . log ( kotlinDelegate ) ;
632+ console . log (
633+ `\n--- Generated: android/src/main/java/${ ANDROID_JAVA_PACKAGE_NAME . replaceAll ( '.' , '/' ) } /NativeBrownfieldNavigationModule.kt ---`
634+ ) ;
635+ console . log ( kotlinModule ) ;
496636 return ;
497637 }
498638
@@ -528,6 +668,24 @@ function main(): void {
528668 'BrownfieldNavigationDelegate.swift'
529669 ) ,
530670 objcImpl : path . join ( PACKAGE_ROOT , 'ios' , 'NativeBrownfieldNavigation.mm' ) ,
671+ kotlinDelegate : path . join (
672+ PACKAGE_ROOT ,
673+ 'android' ,
674+ 'src' ,
675+ 'main' ,
676+ 'java' ,
677+ ...ANDROID_JAVA_PACKAGE_PATH_SEGMENTS ,
678+ 'BrownfieldNavigationDelegate.kt'
679+ ) ,
680+ kotlinModule : path . join (
681+ PACKAGE_ROOT ,
682+ 'android' ,
683+ 'src' ,
684+ 'main' ,
685+ 'java' ,
686+ ...ANDROID_JAVA_PACKAGE_PATH_SEGMENTS ,
687+ 'NativeBrownfieldNavigationModule.kt'
688+ ) ,
531689 } ;
532690
533691 fs . writeFileSync ( paths . turboModuleSpec , turboModuleSpec ) ;
@@ -554,6 +712,12 @@ function main(): void {
554712 fs . writeFileSync ( paths . objcImpl , objcImpl ) ;
555713 console . log ( `Generated: ${ paths . objcImpl } ` ) ;
556714
715+ fs . writeFileSync ( paths . kotlinDelegate , kotlinDelegate ) ;
716+ console . log ( `Generated: ${ paths . kotlinDelegate } ` ) ;
717+
718+ fs . writeFileSync ( paths . kotlinModule , kotlinModule ) ;
719+ console . log ( `Generated: ${ paths . kotlinModule } ` ) ;
720+
557721 console . log ( '\nCodegen complete!' ) ;
558722 console . log ( '' ) ;
559723 console . log ( 'Next steps:' ) ;
0 commit comments