From b1bda039b0038640cbb69d7bc6372bac28ed2a73 Mon Sep 17 00:00:00 2001 From: Hur Ali Date: Wed, 8 Apr 2026 11:59:19 +0500 Subject: [PATCH 1/6] feat(ios): expo updates --- .../project.pbxproj | 4 + .../Brownfield Apple App Expo.xcscheme | 2 +- .../BrownfieldAppleApp.swift | 65 +++++- .../components/ContentView.swift | 21 +- .../components/SettingsScreen.swift | 2 +- apps/AppleApp/Expo.plist | 21 ++ apps/ExpoApp54/RNApp.tsx | 94 +++++++- apps/ExpoApp54/app.json | 23 +- apps/ExpoApp54/app/(tabs)/index.tsx | 122 +++++++++- apps/ExpoApp54/package.json | 4 +- .../ReactBrownfield.podspec | 1 + .../ios/ExpoHostRuntime.swift | 115 +++++++++- .../ios/ReactNativeBrownfield.swift | 83 +++++++ .../ios/ReactNativeHostRuntime.swift | 14 ++ .../ios/ReactNativeViewController.swift | 46 +++- .../ios/withBrownfieldIos.ts | 5 + .../expo-config-plugin/ios/xcodeHelpers.ts | 212 ++++++++++++++++++ .../template/ios/patchExpoPre55.sh | 2 + .../src/expo-config-plugin/withBrownfield.ts | 4 +- yarn.lock | 80 ++++++- 20 files changed, 885 insertions(+), 35 deletions(-) create mode 100644 apps/AppleApp/Expo.plist diff --git a/apps/AppleApp/Brownfield Apple App.xcodeproj/project.pbxproj b/apps/AppleApp/Brownfield Apple App.xcodeproj/project.pbxproj index 78323de0..089f48db 100644 --- a/apps/AppleApp/Brownfield Apple App.xcodeproj/project.pbxproj +++ b/apps/AppleApp/Brownfield Apple App.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 614B23992F50633200CB6363 /* hermesvm.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 614B23902F50633200CB6363 /* hermesvm.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 614B239A2F50633200CB6363 /* ReactBrownfield.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 614B23912F50633200CB6363 /* ReactBrownfield.xcframework */; }; 614B239B2F50633200CB6363 /* ReactBrownfield.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 614B23912F50633200CB6363 /* ReactBrownfield.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 619CC6202F7FCAC300EE3987 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 619CC61F2F7FCAC300EE3987 /* Expo.plist */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -43,6 +44,7 @@ 614B238F2F50633200CB6363 /* BrownfieldNavigation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = BrownfieldNavigation.xcframework; path = package/BrownfieldNavigation.xcframework; sourceTree = ""; }; 614B23902F50633200CB6363 /* hermesvm.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = hermesvm.xcframework; path = package/hermesvm.xcframework; sourceTree = ""; }; 614B23912F50633200CB6363 /* ReactBrownfield.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ReactBrownfield.xcframework; path = package/ReactBrownfield.xcframework; sourceTree = ""; }; + 619CC61F2F7FCAC300EE3987 /* Expo.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; 793C76A72EEBF938008A2A34 /* Brownfield Apple App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Brownfield Apple App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -85,6 +87,7 @@ 793C769E2EEBF938008A2A34 = { isa = PBXGroup; children = ( + 619CC61F2F7FCAC300EE3987 /* Expo.plist */, 793C76A92EEBF938008A2A34 /* Brownfield Apple App */, 6108E5322F40A26800EA8FA1 /* Frameworks */, 793C76A82EEBF938008A2A34 /* Products */, @@ -164,6 +167,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 619CC6202F7FCAC300EE3987 /* Expo.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/Brownfield Apple App Expo.xcscheme b/apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/Brownfield Apple App Expo.xcscheme index 25cbd4e2..47c66618 100644 --- a/apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/Brownfield Apple App Expo.xcscheme +++ b/apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/Brownfield Apple App Expo.xcscheme @@ -31,7 +31,7 @@ shouldAutocreateTestPlan = "YES"> Bool { + print("==== Brownfield: Initializing...") + ReactNativeBrownfield.shared.bundle = ReactNativeBundle + ReactNativeBrownfield.shared.startReactNative { + print("React Native has been loaded") +// ReactNativeBrownfield.shared.initializeExpoUpdates() + } + return ReactNativeBrownfield.shared.application( application, didFinishLaunchingWithOptions: launchOptions ) } + + func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + return ReactNativeBrownfield.shared.application(application, willFinishLaunchingWithOptions: launchOptions) + } + + func applicationDidBecomeActive(_ application: UIApplication) { + ReactNativeBrownfield.shared.applicationDidBecomeActive(application) + } + + func applicationWillResignActive(_ application: UIApplication) { + ReactNativeBrownfield.shared.applicationWillResignActive(application) + } + + func applicationDidEnterBackground(_ application: UIApplication) { + ReactNativeBrownfield.shared.applicationDidEnterBackground(application) + } + + func applicationWillEnterForeground(_ application: UIApplication) { + ReactNativeBrownfield.shared.applicationWillEnterForeground(application) + } + + func applicationWillTerminate(_ application: UIApplication) { + ReactNativeBrownfield.shared.applicationWillTerminate(application) + } + + func applicationDidReceiveMemoryWarning(_ application: UIApplication) { + ReactNativeBrownfield.shared.applicationDidReceiveMemoryWarning(application) + } + + func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { + ReactNativeBrownfield.shared.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler) + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + ReactNativeBrownfield.shared.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: any Error) { + ReactNativeBrownfield.shared.application(application, didFailToRegisterForRemoteNotificationsWithError: error) + } + + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + ReactNativeBrownfield.shared.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler) + } + + func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + ReactNativeBrownfield.shared.application(application, performFetchWithCompletionHandler: completionHandler) + } } public class RNNavigationDelegate: BrownfieldNavigationDelegate { @@ -74,10 +129,12 @@ struct BrownfieldAppleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate init() { - ReactNativeBrownfield.shared.bundle = ReactNativeBundle - ReactNativeBrownfield.shared.startReactNative { - print("React Native has been loaded") - } +// print("==== Brownfield: Initializing...") +// ReactNativeBrownfield.shared.bundle = ReactNativeBundle +// ReactNativeBrownfield.shared.startReactNative { +// print("React Native has been loaded") +//// ReactNativeBrownfield.shared.initializeExpoUpdates() +// } BrownfieldNavigationManager.shared.setDelegate( navigationDelegate: RNNavigationDelegate() diff --git a/apps/AppleApp/Brownfield Apple App/components/ContentView.swift b/apps/AppleApp/Brownfield Apple App/components/ContentView.swift index 7f08507a..2ecdbc6d 100644 --- a/apps/AppleApp/Brownfield Apple App/components/ContentView.swift +++ b/apps/AppleApp/Brownfield Apple App/components/ContentView.swift @@ -15,18 +15,29 @@ let initialState = BrownfieldStore( ) struct ContentView: View { + @State var showRN: Bool = false + var body: some View { NavigationView { VStack(spacing: 16) { GreetingCard(name: "iOS Expo") - MessagesView() +// MessagesView() + + Button("Go to RN") { +// ReactNativeBrownfield.shared.initializeExpoUpdates() +// showRN = true + } + + if (!showRN) { + ReactNativeView(moduleName: "main") + .navigationBarHidden(true) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .background(Color(UIColor.systemBackground)) + } + - ReactNativeView(moduleName: "main") - .navigationBarHidden(true) - .clipShape(RoundedRectangle(cornerRadius: 16)) - .background(Color(UIColor.systemBackground)) } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(16) diff --git a/apps/AppleApp/Brownfield Apple App/components/SettingsScreen.swift b/apps/AppleApp/Brownfield Apple App/components/SettingsScreen.swift index d5f584c3..f8eab904 100644 --- a/apps/AppleApp/Brownfield Apple App/components/SettingsScreen.swift +++ b/apps/AppleApp/Brownfield Apple App/components/SettingsScreen.swift @@ -19,4 +19,4 @@ struct SettingsScreen: View { } .navigationTitle("Settings") } -} \ No newline at end of file +} diff --git a/apps/AppleApp/Expo.plist b/apps/AppleApp/Expo.plist new file mode 100644 index 00000000..f7f32553 --- /dev/null +++ b/apps/AppleApp/Expo.plist @@ -0,0 +1,21 @@ + + + + + EXUpdatesCheckOnLaunch + NEVER + EXUpdatesEnabled + + EXUpdatesLaunchWaitMs + 0 + EXUpdatesRequestHeaders + + expo-channel-name + production + + EXUpdatesRuntimeVersion + 1.0.0 + EXUpdatesURL + https://u.expo.dev/5696d23a-8a54-484c-9a7c-c473d0f28c05 + + diff --git a/apps/ExpoApp54/RNApp.tsx b/apps/ExpoApp54/RNApp.tsx index 5d08feed..a8ba264a 100644 --- a/apps/ExpoApp54/RNApp.tsx +++ b/apps/ExpoApp54/RNApp.tsx @@ -1,25 +1,111 @@ import { SafeAreaView } from 'react-native-safe-area-context'; -import { Button, StyleSheet, Text, View } from 'react-native'; +import { Alert, Button, StyleSheet, Text, View } from 'react-native'; import BrownfieldNavigation from '@callstack/brownfield-navigation'; +import * as Updates from 'expo-updates'; import Counter from './components/counter'; +function withTimeout( + promise: Promise, + ms: number, + label: string +): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error(`${label} timed out after ${ms}ms`)); + }, ms); + + promise + .then((value) => { + clearTimeout(timer); + resolve(value); + }) + .catch((error) => { + clearTimeout(timer); + reject(error); + }); + }); +} + +async function logUpdatesDiagnostics() { + try { + const extra = await Updates.getExtraParamsAsync(); + console.log('== updates diagnostics', { + isEnabled: Updates.isEnabled, + channel: Updates.channel, + runtimeVersion: Updates.runtimeVersion, + updateId: Updates.updateId, + isEmbeddedLaunch: Updates.isEmbeddedLaunch, + emergencyLaunchReason: Updates.emergencyLaunchReason ?? null, + launchDuration: Updates.launchDuration ?? null, + manifestKeys: Object.keys(Updates.manifest ?? {}), + extraParams: extra, + }); + } catch (error) { + console.error('== updates diagnostics error', error); + } +} + +async function checkForUpdate() { + try { + console.log('== checkForUpdateAsync start'); + const update = await withTimeout( + Updates.checkForUpdateAsync(), + 15000, + 'checkForUpdateAsync' + ); + + console.log('== checkForUpdateAsync result', update); + if (update.isAvailable) { + const fetchResult = await Updates.fetchUpdateAsync(); + console.log('== fetchUpdateAsync result', fetchResult); + await Updates.reloadAsync(); + + console.log('== update applied', update); + Alert.alert( + 'Update available', + 'Please restart the app to apply the update' + ); + } else { + Alert.alert('No update available'); + } + } catch (error) { + console.error('== checkForUpdateAsync/fetchUpdateAsync failed', error); + Alert.alert('Update check failed', String(error)); + } +} + export default function RNApp() { + // useEffect(() => { + // logUpdatesDiagnostics(); + // checkForUpdate(); + // }, []); + return ( - Expo React Native Brownfield + 123123 Expo React Native Brownfield