From 79d9166f212fdb8f7fae2a18948b3c950767bfe5 Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Sat, 14 Feb 2026 14:55:31 +0500 Subject: [PATCH 01/24] feat: add BrownfieldNavigation TurboModule and other classes --- .../BrownfieldNavigationDelegate.swift | 5 + .../BrownfieldNavigationManager.swift | 18 + .../NativeBrownfieldNavigation.h | 15 + .../NativeBrownfieldNavigation.mm | 26 + packages/react-native-brownfield/package.json | 3 +- .../src/BrownfieldNavigation.ts | 9 + .../src/NativeBrownfieldNavigation.ts | 9 + packages/react-native-brownfield/src/index.ts | 2 + .../src/scripts/brownfield-navigation.ts | 474 ++++++++++++++++++ yarn.lock | 1 + 10 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 packages/react-native-brownfield/ios/BrownfieldNavigation/BrownfieldNavigationDelegate.swift create mode 100644 packages/react-native-brownfield/ios/BrownfieldNavigation/BrownfieldNavigationManager.swift create mode 100644 packages/react-native-brownfield/ios/BrownfieldNavigation/NativeBrownfieldNavigation.h create mode 100644 packages/react-native-brownfield/ios/BrownfieldNavigation/NativeBrownfieldNavigation.mm create mode 100644 packages/react-native-brownfield/src/BrownfieldNavigation.ts create mode 100644 packages/react-native-brownfield/src/NativeBrownfieldNavigation.ts create mode 100644 packages/react-native-brownfield/src/scripts/brownfield-navigation.ts diff --git a/packages/react-native-brownfield/ios/BrownfieldNavigation/BrownfieldNavigationDelegate.swift b/packages/react-native-brownfield/ios/BrownfieldNavigation/BrownfieldNavigationDelegate.swift new file mode 100644 index 00000000..f4b2828d --- /dev/null +++ b/packages/react-native-brownfield/ios/BrownfieldNavigation/BrownfieldNavigationDelegate.swift @@ -0,0 +1,5 @@ +import Foundation + +@objc public protocol BrownfieldNavigationDelegate: AnyObject { + +} diff --git a/packages/react-native-brownfield/ios/BrownfieldNavigation/BrownfieldNavigationManager.swift b/packages/react-native-brownfield/ios/BrownfieldNavigation/BrownfieldNavigationManager.swift new file mode 100644 index 00000000..4e711aae --- /dev/null +++ b/packages/react-native-brownfield/ios/BrownfieldNavigation/BrownfieldNavigationManager.swift @@ -0,0 +1,18 @@ +// +// BrownfieldNavigationManager.swift +// +// Created by Hur Ali on 10/02/2026. +// + +public class BrownfieldNavigationManager: NSObject { + @objc public static let shared = BrownfieldNavigationManager() + private var navigationDelegate: BrownfieldNavigationDelegate? + + public func setDelegate(navigationDelegate: BrownfieldNavigationDelegate) { + self.navigationDelegate = navigationDelegate + } + + @objc public func getDelegate() -> BrownfieldNavigationDelegate { + return self.navigationDelegate! + } +} diff --git a/packages/react-native-brownfield/ios/BrownfieldNavigation/NativeBrownfieldNavigation.h b/packages/react-native-brownfield/ios/BrownfieldNavigation/NativeBrownfieldNavigation.h new file mode 100644 index 00000000..d420164d --- /dev/null +++ b/packages/react-native-brownfield/ios/BrownfieldNavigation/NativeBrownfieldNavigation.h @@ -0,0 +1,15 @@ +// +// NativeBrownfieldNavigation.h +// +// Created by Hur Ali on 10/02/2026. +// + +#ifdef __cplusplus + +#import + +@interface NativeBrownfieldNavigation : NSObject + +@end + +#endif diff --git a/packages/react-native-brownfield/ios/BrownfieldNavigation/NativeBrownfieldNavigation.mm b/packages/react-native-brownfield/ios/BrownfieldNavigation/NativeBrownfieldNavigation.mm new file mode 100644 index 00000000..5d630d60 --- /dev/null +++ b/packages/react-native-brownfield/ios/BrownfieldNavigation/NativeBrownfieldNavigation.mm @@ -0,0 +1,26 @@ +#import "NativeBrownfieldNavigation.h" + +#if __has_include("ReactBrownfield/ReactBrownfield-Swift.h") +#import "ReactBrownfield/ReactBrownfield-Swift.h" +#else +#import "ReactBrownfield-Swift.h" +#endif + +@implementation NativeBrownfieldNavigation + +- (void)temporary { + NSLog(@"temporary"); +} + +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} + ++ (NSString *)moduleName +{ + return @"NativeBrownfieldNavigation"; +} + +@end diff --git a/packages/react-native-brownfield/package.json b/packages/react-native-brownfield/package.json index 6bbe22a5..628fd166 100644 --- a/packages/react-native-brownfield/package.json +++ b/packages/react-native-brownfield/package.json @@ -4,7 +4,8 @@ "license": "MIT", "author": "Michal Chudziak ", "bin": { - "brownfield": "lib/commonjs/scripts/brownfield.js" + "brownfield": "lib/commonjs/scripts/brownfield.js", + "brownfield-navigation-codegen": "lib/commonjs/scripts/brownfield-navigation.js" }, "contributors": [ "Piotr Drapich " diff --git a/packages/react-native-brownfield/src/BrownfieldNavigation.ts b/packages/react-native-brownfield/src/BrownfieldNavigation.ts new file mode 100644 index 00000000..5af516da --- /dev/null +++ b/packages/react-native-brownfield/src/BrownfieldNavigation.ts @@ -0,0 +1,9 @@ +import NativeBrownfieldNavigation from './NativeBrownfieldNavigation'; + +const BrownfieldNavigation = { + temporary: () => { + NativeBrownfieldNavigation.temporary(); + }, +}; + +export default BrownfieldNavigation; diff --git a/packages/react-native-brownfield/src/NativeBrownfieldNavigation.ts b/packages/react-native-brownfield/src/NativeBrownfieldNavigation.ts new file mode 100644 index 00000000..5d4ed84d --- /dev/null +++ b/packages/react-native-brownfield/src/NativeBrownfieldNavigation.ts @@ -0,0 +1,9 @@ +import { TurboModuleRegistry, type TurboModule } from 'react-native'; + +export interface Spec extends TurboModule { + temporary(): void; +} + +export default TurboModuleRegistry.getEnforcing( + 'NativeBrownfieldNavigation' +); diff --git a/packages/react-native-brownfield/src/index.ts b/packages/react-native-brownfield/src/index.ts index 14024084..84e3108c 100644 --- a/packages/react-native-brownfield/src/index.ts +++ b/packages/react-native-brownfield/src/index.ts @@ -1,6 +1,7 @@ import { Platform } from 'react-native'; import ReactNativeBrownfieldModule from './NativeReactNativeBrownfieldModule'; +import BrownfieldNavigation from './BrownfieldNavigation'; export interface MessageEvent { data: unknown; @@ -87,3 +88,4 @@ const ReactNativeBrownfield = { }; export default ReactNativeBrownfield; +export { BrownfieldNavigation }; diff --git a/packages/react-native-brownfield/src/scripts/brownfield-navigation.ts b/packages/react-native-brownfield/src/scripts/brownfield-navigation.ts new file mode 100644 index 00000000..7de423f7 --- /dev/null +++ b/packages/react-native-brownfield/src/scripts/brownfield-navigation.ts @@ -0,0 +1,474 @@ +#!/usr/bin/env node +/** + * Brownfield Navigation Codegen Script + * + * Reads a user's brownfield.navigation.ts spec file and generates/updates: + * - src/NativeBrownfieldNavigation.ts (TurboModule spec) + * - ios/NativeBrownfieldNavigation.mm (Objective-C++ implementation) + * - ios/BrownfieldNavigationDelegate.swift (Swift delegate protocol) + * - src/index.tsx (Exported JavaScript API) + * + * Usage: + * npx brownfield-navigation-codegen + * + * Example user spec (brownfield.navigation.ts): + * export interface BrownfieldNavigationSpec { + * navigateToProfile(userId: string): void; + * navigateToSettings(): void; + * } + */ + +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +// Resolve script directory in both CJS and ESM runtimes. +// `process.argv[1]` is the executed CLI script path. +const scriptFile = process.argv[1] + ? fs.realpathSync(process.argv[1]) + : path.join(process.cwd(), 'brownfield-navigation.js'); +const scriptDir = path.dirname(scriptFile); + +// Package root is one level up from scripts/ +const PACKAGE_ROOT = path.resolve(scriptDir, '../../../'); + +// ============================================================================ +// Types +// ============================================================================ + +interface MethodParam { + name: string; + type: string; + optional: boolean; +} + +interface MethodSignature { + name: string; + params: MethodParam[]; + returnType: string; + isAsync: boolean; +} + +// ============================================================================ +// TypeScript to Native Type Mapping +// ============================================================================ + +const TS_TO_OBJC_TYPE: Record = { + string: 'NSString *', + number: 'double', + boolean: 'BOOL', + void: 'void', + Object: 'NSDictionary *', +}; + +const TS_TO_SWIFT_TYPE: Record = { + string: 'String', + number: 'Double', + boolean: 'Bool', + void: 'Void', + Object: '[String: Any]', +}; + +function mapTsTypeToObjC(tsType: string, nullable: boolean = false): string { + if (tsType.startsWith('Promise<')) { + return 'void'; + } + + const mapped = TS_TO_OBJC_TYPE[tsType]; + if (mapped) { + if (nullable && mapped.includes('*')) { + return mapped.replace(' *', ' * _Nullable'); + } + return mapped; + } + + return nullable ? 'id _Nullable' : 'id'; +} + +function mapTsTypeToSwift(tsType: string, optional: boolean = false): string { + if (tsType.startsWith('Promise<')) { + const inner = tsType.slice(8, -1); + return mapTsTypeToSwift(inner, optional); + } + + const mapped = TS_TO_SWIFT_TYPE[tsType]; + if (mapped) { + return optional ? `${mapped}?` : mapped; + } + + return optional ? 'Any?' : 'Any'; +} + +// ============================================================================ +// Spec Parser +// ============================================================================ + +function parseUserSpec(filePath: string): MethodSignature[] { + const content = fs.readFileSync(filePath, 'utf-8'); + + // Extract interface body - supports both BrownfieldNavigationSpec and Spec + const interfaceMatch = content.match( + /export interface (?:BrownfieldNavigationSpec|Spec)\s*\{([^}]+)\}/s + ); + if (!interfaceMatch || !interfaceMatch[1]) { + throw new Error( + 'Could not find BrownfieldNavigationSpec or Spec interface in spec file' + ); + } + const interfaceBody = interfaceMatch[1]; + + // Parse methods + const methods: MethodSignature[] = []; + const methodRegex = /(\w+)\s*\(([^)]*)\)\s*:\s*(Promise<[^>]+>|[^;]+)\s*;/g; + + let match; + while ((match = methodRegex.exec(interfaceBody)) !== null) { + const name = match[1]; + const paramsStr = match[2]; + const returnType = match[3]; + + if (!name || !returnType) { + continue; + } + + const params: MethodParam[] = []; + if (paramsStr && paramsStr.trim()) { + const paramParts = paramsStr.split(','); + for (const param of paramParts) { + const paramMatch = param.trim().match(/(\w+)(\?)?:\s*(.+)/); + if (paramMatch && paramMatch[1] && paramMatch[3]) { + params.push({ + name: paramMatch[1], + type: paramMatch[3].trim(), + optional: !!paramMatch[2], + }); + } + } + } + + methods.push({ + name, + params, + returnType: returnType.trim(), + isAsync: returnType.trim().startsWith('Promise<'), + }); + } + + return methods; +} + +// ============================================================================ +// Code Generators +// ============================================================================ + +function generateTurboModuleSpec(methods: MethodSignature[]): string { + const methodSignatures = methods + .map((m) => { + const params = m.params + .map((p) => `${p.name}${p.optional ? '?' : ''}: ${p.type}`) + .join(', '); + return ` ${m.name}(${params}): ${m.returnType};`; + }) + .join('\n'); + + return `import { TurboModuleRegistry, type TurboModule } from 'react-native'; + +export interface Spec extends TurboModule { +${methodSignatures} +} + +export default TurboModuleRegistry.getEnforcing( + 'NativeBrownfieldNavigation' +); +`; +} + +function generateBrownfieldNavigationTS(methods: MethodSignature[]): string { + const functionImplementations = methods + .map((m) => { + const params = m.params + .map((p) => `${p.name}${p.optional ? '?' : ''}: ${p.type}`) + .join(', '); + const args = m.params.map((p) => p.name).join(', '); + const returnType = m.returnType === 'void' ? '' : `: ${m.returnType}`; + + if (m.isAsync) { + return ` ${m.name}: async (${params})${returnType} => { + return NativeBrownfieldNavigation.${m.name}(${args}); + }`; + } + return ` ${m.name}: (${params})${returnType} => { + NativeBrownfieldNavigation.${m.name}(${args}); + }`; + }) + .join(',\n'); + + return `import NativeBrownfieldNavigation from './NativeBrownfieldNavigation'; + +const BrownfieldNavigation = { +${functionImplementations}, +}; + +export default BrownfieldNavigation; +`; +} + +function generateSwiftDelegate(methods: MethodSignature[]): string { + const protocolMethods = methods + .map((m) => { + const params = m.params + .map((p) => { + const swiftType = mapTsTypeToSwift(p.type, p.optional); + return `${p.name}: ${swiftType}`; + }) + .join(', '); + + const returnType = + m.returnType === 'void' + ? '' + : ` -> ${mapTsTypeToSwift(m.returnType, false)}`; + + return ` @objc func ${m.name}(${params})${returnType}`; + }) + .join('\n'); + + return `import Foundation + +@objc public protocol BrownfieldNavigationDelegate: AnyObject { +${protocolMethods} +} +`; +} + +function generateObjCImplementation(methods: MethodSignature[]): string { + const methodImpls = methods + .map((m) => { + if (m.isAsync) { + return generateAsyncObjCMethod(m); + } + return generateSyncObjCMethod(m); + }) + .join('\n\n'); + + return `#import "NativeBrownfieldNavigation.h" + +#if __has_include("ReactBrownfield/ReactBrownfield-Swift.h") +#import "ReactBrownfield/ReactBrownfield-Swift.h" +#else +#import "ReactBrownfield-Swift.h" +#endif + +@implementation NativeBrownfieldNavigation + +${methodImpls} + +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} + ++ (NSString *)moduleName +{ + return @"NativeBrownfieldNavigation"; +} + +@end +`; +} + +function generateSyncObjCMethod(method: MethodSignature): string { + const { name, params, returnType } = method; + + // Build ObjC method signature + let signature = `- (${mapTsTypeToObjC(returnType)})${name}`; + if (params.length > 0) { + signature += params + .map((p, i) => { + const prefix = i === 0 ? ':' : ` ${p.name}:`; + return `${prefix}(${mapTsTypeToObjC(p.type, p.optional)})${p.name}`; + }) + .join(''); + } + + // Build delegate call with Swift-style labels + let delegateCall = `[[[BrownfieldNavigationManager shared] getDelegate] ${name}`; + if (params.length > 0) { + delegateCall += params + .map((p, i) => { + // First param: use "With" prefix for Swift interop + const label = i === 0 ? 'With' + capitalizeFirst(p.name) : p.name; + return `${label}:${p.name}`; + }) + .join(' '); + } + delegateCall += ']'; + + const returnStatement = returnType === 'void' ? '' : 'return '; + + return `${signature} { + ${returnStatement}${delegateCall}; +}`; +} + +function generateAsyncObjCMethod(method: MethodSignature): string { + const { name, params } = method; + + // Build ObjC method signature with resolve/reject blocks + let signature = `- (void)${name}`; + const allParams: Array<{ name: string; type: string; optional: boolean }> = [ + ...params, + { name: 'resolve', type: 'RCTPromiseResolveBlock', optional: false }, + { name: 'reject', type: 'RCTPromiseRejectBlock', optional: false }, + ]; + + signature += ':'; + signature += allParams + .map((p, i) => { + const prefix = i === 0 ? '' : p.name; + const type = + p.type === 'RCTPromiseResolveBlock' + ? 'RCTPromiseResolveBlock' + : p.type === 'RCTPromiseRejectBlock' + ? 'RCTPromiseRejectBlock' + : mapTsTypeToObjC(p.type, p.optional); + return `${prefix}(${type})${p.name}`; + }) + .join(' '); + + return `${signature} { + // TODO: Implement async call to delegate + reject(@"not_implemented", @"${name} is not implemented", nil); +}`; +} + +// ============================================================================ +// Utilities +// ============================================================================ + +function capitalizeFirst(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +// ============================================================================ +// Main +// ============================================================================ + +function main(): void { + const args = process.argv.slice(2); + + if (args.length < 1) { + console.log('Brownfield Navigation Codegen'); + console.log(''); + console.log('Usage:'); + console.log( + ' npx brownfield-navigation-codegen ' + ); + console.log(''); + console.log('Options:'); + console.log(' --dry-run Print generated code without writing files'); + console.log(''); + console.log('Example user spec (brownfield.navigation.ts):'); + console.log(' export interface BrownfieldNavigationSpec {'); + console.log(' navigateToProfile(userId: string): void;'); + console.log(' navigateToSettings(): void;'); + console.log(' }'); + process.exit(1); + } + + const specFile = args[0]; + if (!specFile) { + console.error('Error: No spec file provided'); + process.exit(1); + } + + const dryRun = args.includes('--dry-run'); + + // Resolve spec file path + const specPath = path.isAbsolute(specFile) + ? specFile + : path.resolve(process.cwd(), specFile); + + if (!fs.existsSync(specPath)) { + console.error(`Error: Spec file not found: ${specPath}`); + process.exit(1); + } + + console.log(`Parsing spec file: ${specPath}`); + const methods = parseUserSpec(specPath); + + if (methods.length === 0) { + console.error('Error: No methods found in spec file'); + process.exit(1); + } + + console.log( + `Found ${methods.length} method(s): ${methods + .map((m) => m.name) + .join(', ')}` + ); + + // Generate all files + const turboModuleSpec = generateTurboModuleSpec(methods); + const brownfieldNavigationTS = generateBrownfieldNavigationTS(methods); + const swiftDelegate = generateSwiftDelegate(methods); + const objcImpl = generateObjCImplementation(methods); + + if (dryRun) { + console.log('\n--- Generated: src/NativeBrownfieldNavigation.ts ---'); + console.log(turboModuleSpec); + console.log('\n--- Generated: src/BrownfieldNavigation.ts ---'); + console.log(brownfieldNavigationTS); + console.log('\n--- Generated: ios/BrownfieldNavigationDelegate.swift ---'); + console.log(swiftDelegate); + console.log('\n--- Generated: ios/NativeBrownfieldNavigation.mm ---'); + console.log(objcImpl); + return; + } + + // Write files to package + const paths = { + turboModuleSpec: path.join( + PACKAGE_ROOT, + 'src', + 'NativeBrownfieldNavigation.ts' + ), + navigationTs: path.join(PACKAGE_ROOT, 'src', 'BrownfieldNavigation.ts'), + swiftDelegate: path.join( + PACKAGE_ROOT, + 'ios', + 'BrownfieldNavigation', + 'BrownfieldNavigationDelegate.swift' + ), + objcImpl: path.join( + PACKAGE_ROOT, + 'ios', + 'BrownfieldNavigation', + 'NativeBrownfieldNavigation.mm' + ), + }; + + fs.writeFileSync(paths.turboModuleSpec, turboModuleSpec); + console.log(`\nGenerated: ${paths.turboModuleSpec}`); + + fs.writeFileSync(paths.navigationTs, brownfieldNavigationTS); + console.log(`Generated: ${paths.navigationTs}`); + + fs.writeFileSync(paths.swiftDelegate, swiftDelegate); + console.log(`Generated: ${paths.swiftDelegate}`); + + fs.writeFileSync(paths.objcImpl, objcImpl); + console.log(`Generated: ${paths.objcImpl}`); + + console.log('\nCodegen complete!'); + console.log(''); + console.log('Next steps:'); + console.log('1. Run pod install in your iOS project'); + console.log( + '2. Implement the BrownfieldNavigationDelegate protocol in your native code' + ); + console.log( + '3. Call BrownfieldNavigationManager.shared.setDelegate(yourDelegate) before using the module' + ); +} + +main(); diff --git a/yarn.lock b/yarn.lock index 1a86a49d..87723461 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2410,6 +2410,7 @@ __metadata: "@expo/config-plugins": ^54.0.4 bin: brownfield: lib/commonjs/scripts/brownfield.js + brownfield-navigation-codegen: lib/commonjs/scripts/brownfield-navigation.js languageName: unknown linkType: soft From 531c9fdf84297739b51ef98b8f0ce0d5a848f73a Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Sat, 14 Feb 2026 14:58:54 +0500 Subject: [PATCH 02/24] feat: consume brownfield navigation --- .../BrownfieldAppleApp.swift | 65 +++++++++++++++++++ .../components/ReferralsScreen.swift | 28 ++++++++ .../components/SettingsScreen.swift | 22 +++++++ apps/RNApp/brownfield.navigation.ts | 12 ++++ apps/RNApp/ios/Podfile.lock | 4 +- apps/RNApp/package.json | 3 +- apps/RNApp/src/HomeScreen.tsx | 13 ++++ 7 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 apps/AppleApp/Brownfield Apple App/components/ReferralsScreen.swift create mode 100644 apps/AppleApp/Brownfield Apple App/components/SettingsScreen.swift create mode 100644 apps/RNApp/brownfield.navigation.ts diff --git a/apps/AppleApp/Brownfield Apple App/BrownfieldAppleApp.swift b/apps/AppleApp/Brownfield Apple App/BrownfieldAppleApp.swift index f4eab6a8..ba39c956 100644 --- a/apps/AppleApp/Brownfield Apple App/BrownfieldAppleApp.swift +++ b/apps/AppleApp/Brownfield Apple App/BrownfieldAppleApp.swift @@ -2,6 +2,7 @@ import BrownfieldLib import Brownie import ReactBrownfield import SwiftUI +import UIKit class AppDelegate: NSObject, UIApplicationDelegate { var window: UIWindow? @@ -18,6 +19,55 @@ class AppDelegate: NSObject, UIApplicationDelegate { } } +public class RNNavigationDelegate: BrownfieldNavigationDelegate { + public func navigateToSettings() { + present(SettingsScreen()) + } + + public func navigateToReferrals(userId: String) { + present(ReferralsScreen(userId: userId)) + } + + private func present(_ view: Content) { + DispatchQueue.main.async { + let hostingController = UIHostingController(rootView: view) + + guard let topController = UIApplication.shared.topMostViewController() else { + return + } + + if let navigationController = topController.navigationController { + navigationController.pushViewController(hostingController, animated: true) + return + } + + let navigationController = UINavigationController(rootViewController: hostingController) + topController.present(navigationController, animated: true) + } + } +} + +private extension UIApplication { + func topMostViewController( + base: UIViewController? = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first(where: { $0.isKeyWindow })?.rootViewController + ) -> UIViewController? { + if let navigationController = base as? UINavigationController { + return topMostViewController(base: navigationController.visibleViewController) + } + if let tabBarController = base as? UITabBarController, + let selected = tabBarController.selectedViewController { + return topMostViewController(base: selected) + } + if let presented = base?.presentedViewController { + return topMostViewController(base: presented) + } + return base + } +} + @main struct BrownfieldAppleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @@ -27,6 +77,21 @@ struct BrownfieldAppleApp: App { ReactNativeBrownfield.shared.startReactNative { print("React Native has been loaded") } + +// let mgr = ReactBrownfield.BrownfieldNavigationManager.shared +// print("SWIFT mgr:", Unmanaged.passUnretained(mgr).toOpaque()) +// print("SWIFT class:", NSStringFromClass(type(of: mgr))) +// print("SWIFT bundle:", Bundle(for: ReactBrownfield.BrownfieldNavigationManager.self).bundlePath) +// ReactBrownfield.BrownfieldNavigationManager.shared.setDelegate(navigationDelegate: RNNavigationDelegate()) + + let mgr = BrownfieldNavigationManager.shared + print("11 SWIFT mgr:", Unmanaged.passUnretained(mgr).toOpaque()) + print("11 SWIFT class:", NSStringFromClass(type(of: mgr))) + print("11 SWIFT bundle:", Bundle(for: BrownfieldNavigationManager.self).bundlePath) + + mgr.setDelegate( + navigationDelegate: RNNavigationDelegate() + ) #if USE_EXPO_HOST ReactNativeBrownfield.shared.ensureExpoModulesProvider() diff --git a/apps/AppleApp/Brownfield Apple App/components/ReferralsScreen.swift b/apps/AppleApp/Brownfield Apple App/components/ReferralsScreen.swift new file mode 100644 index 00000000..e1f37d1b --- /dev/null +++ b/apps/AppleApp/Brownfield Apple App/components/ReferralsScreen.swift @@ -0,0 +1,28 @@ +import SwiftUI + + +struct ReferralsScreen: View { + let userId: String + + var body: some View { + VStack(spacing: 16) { + Text("Referrals") + .font(.title2) + .fontWeight(.semibold) + + Text("User ID") + .foregroundStyle(.secondary) + Text(userId) + .font(.body.monospaced()) + .textSelection(.enabled) + + Button("Share referral link") { + // Placeholder action for the sample app. + } + .buttonStyle(.borderedProminent) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding() + .navigationTitle("Referrals") + } +} \ No newline at end of file diff --git a/apps/AppleApp/Brownfield Apple App/components/SettingsScreen.swift b/apps/AppleApp/Brownfield Apple App/components/SettingsScreen.swift new file mode 100644 index 00000000..d5f584c3 --- /dev/null +++ b/apps/AppleApp/Brownfield Apple App/components/SettingsScreen.swift @@ -0,0 +1,22 @@ +import SwiftUI + +struct SettingsScreen: View { + var body: some View { + Form { + Section("Preferences") { + Toggle("Enable notifications", isOn: .constant(true)) + Toggle("Dark mode", isOn: .constant(false)) + } + + Section("About") { + HStack { + Text("Version") + Spacer() + Text("1.0.0") + .foregroundStyle(.secondary) + } + } + } + .navigationTitle("Settings") + } +} \ No newline at end of file diff --git a/apps/RNApp/brownfield.navigation.ts b/apps/RNApp/brownfield.navigation.ts new file mode 100644 index 00000000..8bc90246 --- /dev/null +++ b/apps/RNApp/brownfield.navigation.ts @@ -0,0 +1,12 @@ +export interface BrownfieldNavigationSpec { + /** + * Navigate to the native settings screen + */ + navigateToSettings(): void; + + /** + * Navigate to the native referrals screen + * @param userId - The user's unique identifier + */ + navigateToReferrals(userId: string): void; +} diff --git a/apps/RNApp/ios/Podfile.lock b/apps/RNApp/ios/Podfile.lock index 73debef9..d93fc036 100644 --- a/apps/RNApp/ios/Podfile.lock +++ b/apps/RNApp/ios/Podfile.lock @@ -2848,8 +2848,8 @@ SPEC CHECKSUMS: ReactCommon: 804dc80944fa90b86800b43c871742ec005ca424 RNScreens: ffbb0296608eb3560de641a711bbdb663ed1f6b4 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 8e01cef9947ca77f0477a098f0b32848a8e448c6 + Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb PODFILE CHECKSUM: 7c116a16dd0744063c8c6293dbfc638c9d447c19 -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/apps/RNApp/package.json b/apps/RNApp/package.json index 07ea82c4..4c818bdb 100644 --- a/apps/RNApp/package.json +++ b/apps/RNApp/package.json @@ -13,7 +13,8 @@ "lint": "eslint .", "start": "react-native start", "test": "jest", - "codegen": "brownfield codegen" + "codegen": "brownfield codegen", + "brownfield:navigation-codegen": "brownfield-navigation-codegen brownfield.navigation.ts" }, "dependencies": { "@callstack/brownie": "workspace:^", diff --git a/apps/RNApp/src/HomeScreen.tsx b/apps/RNApp/src/HomeScreen.tsx index 75638c26..b0e3c115 100644 --- a/apps/RNApp/src/HomeScreen.tsx +++ b/apps/RNApp/src/HomeScreen.tsx @@ -11,6 +11,7 @@ import { import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import ReactNativeBrownfield from '@callstack/react-native-brownfield'; import type { MessageEvent } from '@callstack/react-native-brownfield'; +import { BrownfieldNavigation } from '@callstack/react-native-brownfield'; import { getRandomTheme } from './utils'; import type { RootStackParamList } from './navigation/RootStack'; @@ -170,6 +171,18 @@ export function HomeScreen({ color={colors.secondary} title="Go back" /> + +