diff --git a/.changeset/bright-android-performance.md b/.changeset/bright-android-performance.md new file mode 100644 index 000000000..ee3b3f652 --- /dev/null +++ b/.changeset/bright-android-performance.md @@ -0,0 +1,6 @@ +--- +'@callstack/react-native-brownfield': major +--- + +Add Android startup, TTID/TTFD, and JS/UI responsiveness metrics. Android's +`OnJSBundleLoaded` callback now receives `JSBundleTimings` instead of a Boolean. diff --git a/apps/AndroidApp/app/src/expo57/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt b/apps/AndroidApp/app/src/expo57/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt index 45f2d46d1..41351b71c 100644 --- a/apps/AndroidApp/app/src/expo57/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt +++ b/apps/AndroidApp/app/src/expo57/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt @@ -3,4 +3,5 @@ package com.callstack.brownfield.android.example object ReactNativeConstants { const val MAIN_MODULE_NAME = "main" const val APP_NAME = "Android (Expo 57)" + const val WAIT_FOR_FULL_DISPLAY = true } diff --git a/apps/AndroidApp/app/src/expo58/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt b/apps/AndroidApp/app/src/expo58/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt index 184b3982e..c25e1529d 100644 --- a/apps/AndroidApp/app/src/expo58/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt +++ b/apps/AndroidApp/app/src/expo58/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt @@ -3,4 +3,5 @@ package com.callstack.brownfield.android.example object ReactNativeConstants { const val MAIN_MODULE_NAME = "main" const val APP_NAME = "Android (Expo 58)" + const val WAIT_FOR_FULL_DISPLAY = true } diff --git a/apps/AndroidApp/app/src/expopreview/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt b/apps/AndroidApp/app/src/expopreview/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt index 61214dd58..96cff50e1 100644 --- a/apps/AndroidApp/app/src/expopreview/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt +++ b/apps/AndroidApp/app/src/expopreview/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt @@ -3,4 +3,5 @@ package com.callstack.brownfield.android.example object ReactNativeConstants { const val MAIN_MODULE_NAME = "main" const val APP_NAME = "Android (Expo Preview)" + const val WAIT_FOR_FULL_DISPLAY = true } diff --git a/apps/AndroidApp/app/src/main/AndroidManifest.xml b/apps/AndroidApp/app/src/main/AndroidManifest.xml index b7c0735e8..c6f1665df 100644 --- a/apps/AndroidApp/app/src/main/AndroidManifest.xml +++ b/apps/AndroidApp/app/src/main/AndroidManifest.xml @@ -18,6 +18,10 @@ android:name=".ReferralsActivity" android:exported="false" android:theme="@style/Theme.AndroidBrownfieldApp" /> + "unavailable" + is Double -> format(value) + is Float -> format(value.toDouble()) + is Long -> format(value.toDouble()) + is Int -> format(value.toDouble()) + is Number -> format(value.toDouble()) + else -> value.toString() + } + + private fun format(value: Double): String = "%.2f".format(value) +} diff --git a/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/E2eTestIds.kt b/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/E2eTestIds.kt index aa71ee4be..d9846cd7b 100644 --- a/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/E2eTestIds.kt +++ b/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/E2eTestIds.kt @@ -3,6 +3,7 @@ package com.callstack.brownfield.android.example /** Keep in sync with `@callstack/brownfield-example-shared-tests` `e2eTestIds`. */ object E2eTestIds { const val nativeAppGreeting = "brownfield-e2e-appleapp-greeting" + const val nativeAppOpenReactNative = "brownfield-e2e-appleapp-open-react-native" const val nativeAppPostMessageSend = "brownfield-e2e-appleapp-post-message-send" const val nativeAppPostMessageToast = "brownfield-e2e-appleapp-post-message-toast" const val nativeAppNativeSettings = "brownfield-e2e-appleapp-native-settings" diff --git a/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt b/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt index fca8dd710..912856921 100644 --- a/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt +++ b/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt @@ -3,27 +3,23 @@ package com.callstack.brownfield.android.example import android.app.Activity import android.content.Intent import android.content.res.Configuration -import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Button import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -31,26 +27,18 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.fragment.compose.AndroidFragment +import com.callstack.brownfield.android.example.components.EspressoTagAnchor import com.callstack.brownfield.android.example.components.GreetingCard import com.callstack.brownfield.android.example.components.PostMessageCard import com.callstack.brownfield.android.example.components.PostMessageToast import com.callstack.brownfield.android.example.ui.theme.AndroidBrownfieldAppTheme -import com.callstack.nativebrownfieldnavigation.BrownfieldNavigationDelegate -import com.callstack.nativebrownfieldnavigation.BrownfieldNavigationManager -import com.callstack.nativebrownfieldnavigation.UserType -import com.callstack.reactnativebrownfield.ReactNativeFragment -import com.callstack.reactnativebrownfield.constants.ReactNativeFragmentArgNames -import com.facebook.react.bridge.Callback -import com.facebook.react.bridge.Promise - -class MainActivity : AppCompatActivity(), BrownfieldNavigationDelegate { + +class MainActivity : AppCompatActivity() { private val isDetoxE2E: Boolean get() = intent?.getStringExtra("DetoxE2E") == "YES" @@ -62,19 +50,11 @@ class MainActivity : AppCompatActivity(), BrownfieldNavigationDelegate { override fun onResume() { super.onResume() - // Own Brownfield navigation only while this activity is foregrounded. - BrownfieldNavigationManager.setDelegate(this) if (isDetoxE2E) { window.decorView.post { window.decorView.requestFocus() } } } - override fun onPause() { - // Release ownership before another host can become the active delegate. - BrownfieldNavigationManager.clearDelegate() - super.onPause() - } - override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (!hasFocus && isDetoxE2E) { @@ -87,7 +67,7 @@ class MainActivity : AppCompatActivity(), BrownfieldNavigationDelegate { enableEdgeToEdge() if (savedInstanceState == null) { - ReactNativeHostManager.initialize(application) { + ReactNativeHostManager.initialize(application) { _ -> if (!isDetoxE2E) { Toast.makeText( this, @@ -115,40 +95,6 @@ class MainActivity : AppCompatActivity(), BrownfieldNavigationDelegate { } } } - - override fun navigateToSettings(user: UserType) { - startActivity(Intent(this, SettingsActivity::class.java)) - } - - override fun navigateToReferrals(userId: String) { - startActivity( - Intent(this, ReferralsActivity::class.java).putExtra( - ReferralsActivity.EXTRA_USER_ID, - userId - ) - ) - } - - override fun requestNativeConfirmation(title: String, promise: Promise) { - runOnUiThread { - AlertDialog.Builder(this) - .setTitle(title) - .setPositiveButton("OK") { _, _ -> promise.resolve(true) } - .setNegativeButton("Cancel") { _, _ -> promise.resolve(false) } - .setCancelable(false) - .show() - } - } - - override fun showNativeBanner(message: String, onDismiss: Callback) { - runOnUiThread { - AlertDialog.Builder(this) - .setMessage(message) - .setPositiveButton("Dismiss") { _, _ -> onDismiss.invoke() } - .setCancelable(false) - .show() - } - } } @Composable @@ -173,20 +119,28 @@ private fun MainScreen(modifier: Modifier = Modifier) { name = ReactNativeConstants.APP_NAME, ) + EspressoTagAnchor(tag = E2eTestIds.nativeAppOpenReactNative) { + Button( + onClick = { + activity?.let { + it.startActivity( + Intent(it, ReactNativeActivity::class.java) + // Forward the Detox flag so the RN host keeps E2E behaviour. + .putExtra("DetoxE2E", it.intent?.getStringExtra("DetoxE2E")) + ) + } + } + ) { + Text("Open React Native") + } + } + PostMessageCard( onMessageReceived = { message -> postMessageToastText = message }, textInputEnabled = !isDetoxE2E, ) - Spacer(modifier = Modifier.height(1.dp)) - - ReactNativeView( - modifier = Modifier - .fillMaxWidth() - .height(520.dp) - .clip(RoundedCornerShape(16.dp)) - .background(MaterialTheme.colorScheme.surface) - ) + Spacer(modifier = Modifier.height(16.dp)) } postMessageToastText?.let { message -> @@ -198,36 +152,6 @@ private fun MainScreen(modifier: Modifier = Modifier) { } } -@Composable -fun ReactNativeView( - modifier: Modifier = Modifier -) { - val activity = LocalContext.current as? Activity - val brownfieldE2E = remember(activity) { - activity?.intent?.getStringExtra("DetoxE2E") == "YES" - } - - AndroidFragment( - modifier = modifier, - arguments = Bundle().apply { - putString( - ReactNativeFragmentArgNames.ARG_MODULE_NAME, - ReactNativeConstants.MAIN_MODULE_NAME - ) - putBundle( - ReactNativeFragmentArgNames.ARG_LAUNCH_OPTIONS, - Bundle().apply { - putString( - "nativeOsVersionLabel", - "Android ${Build.VERSION.RELEASE}" - ) - putBoolean("brownfieldE2E", brownfieldE2E) - } - ) - } - ) -} - @Preview(showBackground = true) @Composable fun GreetingPreview() { diff --git a/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/ReactNativeActivity.kt b/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/ReactNativeActivity.kt new file mode 100644 index 000000000..d942ef86b --- /dev/null +++ b/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/ReactNativeActivity.kt @@ -0,0 +1,171 @@ +package com.callstack.brownfield.android.example + +import android.app.Activity +import android.content.Intent +import android.content.res.Configuration +import android.os.Build +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentFactory +import androidx.fragment.compose.AndroidFragment +import com.callstack.brownfield.android.example.ui.theme.AndroidBrownfieldAppTheme +import com.callstack.nativebrownfieldnavigation.BrownfieldNavigationDelegate +import com.callstack.nativebrownfieldnavigation.BrownfieldNavigationManager +import com.callstack.nativebrownfieldnavigation.UserType +import com.callstack.reactnativebrownfield.OnDisplayMetrics +import com.callstack.reactnativebrownfield.ReactNativeFragment +import com.callstack.reactnativebrownfield.constants.ReactNativeFragmentArgNames +import com.facebook.react.bridge.Callback +import com.facebook.react.bridge.Promise + +/** + * Full-screen React Native host, pushed from [MainActivity] (mirrors RNAppScreen on iOS). + * Each launch creates a new fragment, so every presentation gets its own display metrics. + */ +class ReactNativeActivity : AppCompatActivity(), BrownfieldNavigationDelegate { + private val isDetoxE2E: Boolean + get() = intent?.getStringExtra("DetoxE2E") == "YES" + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + + ReactNativeHostManager.onConfigurationChanged(application, newConfig) + } + + override fun onResume() { + super.onResume() + // Own Brownfield navigation only while this activity is foregrounded. + BrownfieldNavigationManager.setDelegate(this) + if (isDetoxE2E) { + window.decorView.post { window.decorView.requestFocus() } + } + } + + override fun onPause() { + // Release ownership before another host can become the active delegate. + BrownfieldNavigationManager.clearDelegate() + super.onPause() + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (!hasFocus && isDetoxE2E) { + window.decorView.post { window.decorView.requestFocus() } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + supportFragmentManager.fragmentFactory = ReactNativeFragmentFactory() + super.onCreate(null) + enableEdgeToEdge() + + setContent { + AndroidBrownfieldAppTheme { + ReactNativeView( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + .semantics { testTagsAsResourceId = true } + ) + } + } + } + + override fun navigateToSettings(user: UserType) { + startActivity(Intent(this, SettingsActivity::class.java)) + } + + override fun navigateToReferrals(userId: String) { + startActivity( + Intent(this, ReferralsActivity::class.java).putExtra( + ReferralsActivity.EXTRA_USER_ID, + userId + ) + ) + } + + override fun requestNativeConfirmation(title: String, promise: Promise) { + runOnUiThread { + AlertDialog.Builder(this) + .setTitle(title) + .setPositiveButton("OK") { _, _ -> promise.resolve(true) } + .setNegativeButton("Cancel") { _, _ -> promise.resolve(false) } + .setCancelable(false) + .show() + } + } + + override fun showNativeBanner(message: String, onDismiss: Callback) { + runOnUiThread { + AlertDialog.Builder(this) + .setMessage(message) + .setPositiveButton("Dismiss") { _, _ -> onDismiss.invoke() } + .setCancelable(false) + .show() + } + } +} + +@Composable +private fun ReactNativeView( + modifier: Modifier = Modifier +) { + val activity = LocalContext.current as? Activity + val brownfieldE2E = remember(activity) { + activity?.intent?.getStringExtra("DetoxE2E") == "YES" + } + + AndroidFragment( + modifier = modifier, + arguments = Bundle().apply { + putString( + ReactNativeFragmentArgNames.ARG_MODULE_NAME, + ReactNativeConstants.MAIN_MODULE_NAME + ) + putBoolean( + ReactNativeFragmentArgNames.ARG_WAIT_FOR_FULL_DISPLAY, + ReactNativeConstants.WAIT_FOR_FULL_DISPLAY + ) + putBoolean( + ReactNativeFragmentArgNames.ARG_COLLECT_THREAD_METRICS, + true + ) + putBundle( + ReactNativeFragmentArgNames.ARG_LAUNCH_OPTIONS, + Bundle().apply { + putString( + "nativeOsVersionLabel", + "Android ${Build.VERSION.RELEASE}" + ) + putBoolean("brownfieldE2E", brownfieldE2E) + } + ) + }, + onUpdate = { fragment -> + fragment.onMetrics = OnDisplayMetrics(BrownfieldMetricsLogger::logDisplay) + } + ) +} + +private class ReactNativeFragmentFactory : FragmentFactory() { + override fun instantiate(classLoader: ClassLoader, className: String): Fragment { + if (className == ReactNativeFragment::class.java.name) { + return ReactNativeFragment().apply { + onMetrics = OnDisplayMetrics(BrownfieldMetricsLogger::logDisplay) + } + } + return super.instantiate(classLoader, className) + } +} diff --git a/apps/AndroidApp/app/src/vanilla/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt b/apps/AndroidApp/app/src/vanilla/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt index a353f1868..4e51037de 100644 --- a/apps/AndroidApp/app/src/vanilla/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt +++ b/apps/AndroidApp/app/src/vanilla/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt @@ -3,4 +3,5 @@ package com.callstack.brownfield.android.example object ReactNativeConstants { const val MAIN_MODULE_NAME = "RNApp" const val APP_NAME = "Android" + const val WAIT_FOR_FULL_DISPLAY = true } diff --git a/apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/Brownfield Apple App Vanilla.xcscheme b/apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/Brownfield Apple App Vanilla.xcscheme index bb2231e3a..57b9711a3 100644 --- a/apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/Brownfield Apple App Vanilla.xcscheme +++ b/apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/Brownfield Apple App Vanilla.xcscheme @@ -31,7 +31,7 @@ shouldAutocreateTestPlan = "YES"> String { + // busyRatio describes time beyond frame deadlines, not CPU utilization. + "fps=\(number(metrics.fps)) busyRatio=\(number(metrics.busyRatio)) sampledMs=\(String(format: "%.2f", metrics.sampledMs)) frames=\(metrics.frameCount)" + } + + private static func number(_ value: NSNumber?) -> String { + guard let value else { return "unavailable" } + return String(format: "%.2f", value.doubleValue) + } +} diff --git a/apps/AppleApp/Brownfield Apple App/E2eTestIds.swift b/apps/AppleApp/Brownfield Apple App/E2eTestIds.swift index c4fe83d8b..bcccc7f2b 100644 --- a/apps/AppleApp/Brownfield Apple App/E2eTestIds.swift +++ b/apps/AppleApp/Brownfield Apple App/E2eTestIds.swift @@ -3,6 +3,7 @@ import Foundation /// Keep in sync with `@callstack/brownfield-example-shared-tests` `e2eTestIds`. enum E2eTestIds { static let appleAppGreeting = "brownfield-e2e-appleapp-greeting" + static let appleAppOpenReactNative = "brownfield-e2e-appleapp-open-react-native" static let appleAppPostMessageSend = "brownfield-e2e-appleapp-post-message-send" static let appleAppPostMessageToast = "brownfield-e2e-appleapp-post-message-toast" static let appleAppNativeSettings = "brownfield-e2e-appleapp-native-settings" diff --git a/apps/AppleApp/Brownfield Apple App/components/ContentView.swift b/apps/AppleApp/Brownfield Apple App/components/ContentView.swift index 8a78d84f9..4ab83153c 100644 --- a/apps/AppleApp/Brownfield Apple App/components/ContentView.swift +++ b/apps/AppleApp/Brownfield Apple App/components/ContentView.swift @@ -1,7 +1,6 @@ import Brownie import ReactBrownfield import SwiftUI -import UIKit struct ChatMessage: Identifiable { let id: Int @@ -16,10 +15,8 @@ let initialState = BrownfieldStore( #if USE_EXPO_HOST private let hostAppName = "iOS Expo" -private let reactNativeModuleName = "main" #else private let hostAppName = "iOS Vanilla" -private let reactNativeModuleName = "RNApp" #endif private func brownfieldPostMessageText(from raw: String) -> String { @@ -32,47 +29,37 @@ private func brownfieldPostMessageText(from raw: String) -> String { return raw } -private var brownfieldInitialProperties: [String: Any] { - [ - "nativeOsVersionLabel": - "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)", - "brownfieldE2E": ProcessInfo.processInfo.arguments.contains("-DetoxE2E"), - ] -} - struct ContentView: View { @State private var messageObserver: NSObjectProtocol? @State private var showPostMessageToast = false @State private var postMessageToastText = "" var body: some View { - NavigationView { - ZStack { + // Toast sits above NavigationView so RN postMessage stays visible on the pushed RN screen. + ZStack { + NavigationView { ScrollView { VStack(spacing: 16) { GreetingCard(name: hostAppName) - MessagesView() + NavigationLink(destination: RNAppScreen()) { + Text("Open React Native") + } + .buttonStyle(.borderedProminent) + .accessibilityIdentifier(E2eTestIds.appleAppOpenReactNative) - ReactNativeView( - moduleName: reactNativeModuleName, - initialProperties: brownfieldInitialProperties - ) - .navigationBarHidden(true) - .clipShape(RoundedRectangle(cornerRadius: 16)) - .background(Color(UIColor.systemBackground)) - .frame(minHeight: 520) + MessagesView() } .frame(maxWidth: .infinity) .padding(16) } + } - if showPostMessageToast { - Toast( - message: postMessageToastText, - isShowing: $showPostMessageToast - ) - } + if showPostMessageToast { + Toast( + message: postMessageToastText, + isShowing: $showPostMessageToast + ) } } .onAppear { diff --git a/apps/AppleApp/Brownfield Apple App/components/RNAppScreen.swift b/apps/AppleApp/Brownfield Apple App/components/RNAppScreen.swift new file mode 100644 index 000000000..217f2749d --- /dev/null +++ b/apps/AppleApp/Brownfield Apple App/components/RNAppScreen.swift @@ -0,0 +1,34 @@ +import ReactBrownfield +import SwiftUI +import UIKit + +#if USE_EXPO_HOST +private let reactNativeModuleName = "main" +// The Expo example bundles do not yet send the JS full-display marker. +private let waitForFullDisplay = false +#else +private let reactNativeModuleName = "RNApp" +private let waitForFullDisplay = true +#endif + +private var brownfieldInitialProperties: [String: Any] { + [ + "nativeOsVersionLabel": + "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)", + "brownfieldE2E": ProcessInfo.processInfo.arguments.contains("-DetoxE2E"), + ] +} + +struct RNAppScreen: View { + var body: some View { + ReactNativeView( + moduleName: reactNativeModuleName, + initialProperties: brownfieldInitialProperties, + waitForFullDisplay: waitForFullDisplay, + collectThreadMetrics: true, + onMetrics: BrownfieldMetricsLogger.logDisplay + ) + .navigationBarHidden(true) + .ignoresSafeArea() + } +} diff --git a/apps/ExpoApp57/RNApp.tsx b/apps/ExpoApp57/RNApp.tsx index 0ced41f70..54c987449 100644 --- a/apps/ExpoApp57/RNApp.tsx +++ b/apps/ExpoApp57/RNApp.tsx @@ -4,12 +4,20 @@ import BrownfieldNavigation from '@callstack/brownfield-navigation'; import { checkAndFetchUpdate } from './src/utils/expo-rn-updates'; import Counter from './src/components/counter'; +import ReactNativeBrownfield from '@callstack/react-native-brownfield'; +import { useMarkFullyDisplayed } from './src/hooks/use-mark-fully-displayed'; type RNAppProps = { nativeOsVersionLabel?: string; + brownfieldPresentationID?: string; }; -export default function RNApp({ nativeOsVersionLabel }: RNAppProps) { +export default function RNApp({ + nativeOsVersionLabel, + brownfieldPresentationID, +}: RNAppProps) { + useMarkFullyDisplayed(brownfieldPresentationID); + const requestNativeConfirmation = async () => { try { await BrownfieldNavigation.requestNativeConfirmation( @@ -57,17 +65,18 @@ export default function RNApp({ nativeOsVersionLabel }: RNAppProps) { title="Navigate to Referrals" onPress={() => BrownfieldNavigation.navigateToReferrals('123')} /> -