diff --git a/.changeset/small-clouds-train.md b/.changeset/small-clouds-train.md new file mode 100644 index 00000000..a68c9bd7 --- /dev/null +++ b/.changeset/small-clouds-train.md @@ -0,0 +1,6 @@ +--- +'@callstack/brownfield-navigation': minor +'@callstack/brownfield-cli': minor +--- + +feat: add support for callback and promise to brownfield navigation 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 6113b2d8..b1388796 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 @@ -9,6 +9,7 @@ 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 @@ -37,6 +38,8 @@ 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 { override fun onConfigurationChanged(newConfig: Configuration) { @@ -108,6 +111,27 @@ class MainActivity : AppCompatActivity(), BrownfieldNavigationDelegate { ) ) } + + 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 @@ -117,11 +141,11 @@ private fun MainScreen(modifier: Modifier = Modifier) { verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally // center top bar content ) { - Spacer(modifier = Modifier.height(3.dp)) + Spacer(modifier = Modifier.height(3.dp)) - GreetingCard( - name = ReactNativeConstants.APP_NAME, - ) + GreetingCard( + name = ReactNativeConstants.APP_NAME, + ) PostMessageCard() diff --git a/apps/AppleApp/Brownfield Apple App/BrownfieldAppleApp.swift b/apps/AppleApp/Brownfield Apple App/BrownfieldAppleApp.swift index 0e131026..6c60a6a9 100644 --- a/apps/AppleApp/Brownfield Apple App/BrownfieldAppleApp.swift +++ b/apps/AppleApp/Brownfield Apple App/BrownfieldAppleApp.swift @@ -44,6 +44,50 @@ public class RNNavigationDelegate: BrownfieldNavigationDelegate { present(ReferralsScreen(userId: userId)) } + public func requestNativeConfirmation( + _ title: String, + resolve: @escaping (Any?) -> Void, + reject: @escaping (String?, String?, (any Error)?) -> Void + ) { + DispatchQueue.main.async { + guard let topController = UIApplication.shared.topMostViewController() else { + reject( + "no_view_controller", + "Could not find a view controller to present the confirmation.", + nil + ) + return + } + + let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in + resolve(true) + }) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in + resolve(false) + }) + topController.present(alert, animated: true) + } + } + + public func showNativeBanner( + _ message: String, + onDismiss onDismiss: @escaping ([Any]?) -> Void + ) { + DispatchQueue.main.async { + guard let topController = UIApplication.shared.topMostViewController() else { + onDismiss([]) + return + } + + let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Dismiss", style: .default) { _ in + onDismiss([]) + }) + topController.present(alert, animated: true) + } + } + private func present(_ view: Content) { DispatchQueue.main.async { let hostingController = UIHostingController(rootView: view) diff --git a/apps/ExpoApp55/RNApp.tsx b/apps/ExpoApp55/RNApp.tsx index e8a29731..559c3afc 100644 --- a/apps/ExpoApp55/RNApp.tsx +++ b/apps/ExpoApp55/RNApp.tsx @@ -1,25 +1,30 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { Button, StyleSheet, Text, View } from 'react-native'; -import { useEffect } from 'react'; import BrownfieldNavigation from '@callstack/brownfield-navigation'; -import { - syncBrownfieldE2EModeFromRootProps, - type BrownfieldRootProps, -} from '@callstack/brownfield-example-shared-tests/runtime'; import { checkAndFetchUpdate } from './src/utils/expo-rn-updates'; import Counter from './src/components/counter'; -type RNAppProps = BrownfieldRootProps; +type RNAppProps = { + nativeOsVersionLabel?: string; +}; -export default function RNApp({ - nativeOsVersionLabel, - brownfieldE2E, -}: RNAppProps) { - useEffect(() => { - syncBrownfieldE2EModeFromRootProps(brownfieldE2E); - return () => syncBrownfieldE2EModeFromRootProps(undefined); - }, [brownfieldE2E]); +export default function RNApp({ nativeOsVersionLabel }: RNAppProps) { + const requestNativeConfirmation = async () => { + try { + await BrownfieldNavigation.requestNativeConfirmation( + 'Are you sure you want to continue?' + ); + } catch (error) { + console.error(`Native confirmation failed: ${error}`, 'native'); + } + }; + + const showNativeBanner = () => { + BrownfieldNavigation.showNativeBanner('Hello from React Native!', () => { + console.log('Native banner dismissed'); + }); + }; return ( @@ -39,12 +44,29 @@ export default function RNApp({