From b9514865d909c156d75ea530ab5777c21a61e7d1 Mon Sep 17 00:00:00 2001 From: Patryk Mleczek <67064618+pmleczek@users.noreply.github.com> Date: Fri, 20 Feb 2026 23:27:51 +0100 Subject: [PATCH] [brownfield][android] add basic implementation of shared state (#43097) # Why Shared state, while implementable with messaging API, would require some amount of boilerplate code to be added by developer. We can introduce simple key-value (and shared object based) store which will provide seamless experience of data synchronization between the brownfield and RN apps. It can be extended with property wrappers in follow ups to provide even more seamless experience in the native apps # How Added an API which allows for `useState` like shared state observing and setting in the JS and subscriber pattern in native platforms (Android for now, iOS will be implemented in follow ups). To user the API feels like a seamless synchronization between (only in JS for now, but I plan to implement things like property wrappers in follow ups): https://github.com/user-attachments/assets/6fc86358-5f80-4d11-b254-3e43218aa2dd Also, simplified the RN activity in the test app as it has now grown pretty big and added a screen for testing of the new API # Test Plan Tested manually using the newly added screen as visible on the video above. Native app updates clock every second and JS app controls the value of counter which is doubled in the native side on change # Checklist - [X] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [X] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). --- .../android-integrated/app/build.gradle.kts | 2 +- .../BrownfieldTestActivity.kt | 106 ++++++++++++++ .../MainActivity.kt | 52 +++---- .../ReactNativeActivity.kt | 79 ++-------- .../expo-app/src/app/apis/index.tsx | 10 +- .../expo-app/src/app/apis/state.tsx | 136 ++++++++++++++++++ packages/expo-brownfield/CHANGELOG.md | 2 + .../brownfield/ExpoBrownfieldStateModule.kt | 26 ++++ .../expo/modules/brownfield/SharedState.kt | 39 +++++ .../java/expo/modules/brownfield/State.kt | 39 +++++ .../build/ExpoBrownfieldModule.d.ts | 62 +++++++- .../build/ExpoBrownfieldModule.d.ts.map | 2 +- .../build/ExpoBrownfieldModule.js | 75 +++++++++- .../build/ExpoBrownfieldModule.js.map | 2 +- ...s.d.ts => ExpoBrownfieldModule.types.d.ts} | 6 +- .../build/ExpoBrownfieldModule.types.d.ts.map | 1 + .../build/ExpoBrownfieldModule.types.js | 2 + .../build/ExpoBrownfieldModule.types.js.map | 1 + .../build/ExpoBrownfieldStateModule.d.ts | 38 +++++ .../build/ExpoBrownfieldStateModule.d.ts.map | 1 + .../build/ExpoBrownfieldStateModule.js | 91 ++++++++++++ .../build/ExpoBrownfieldStateModule.js.map | 1 + .../ExpoBrownfieldStateModule.types.d.ts | 6 + .../ExpoBrownfieldStateModule.types.d.ts.map | 1 + .../build/ExpoBrownfieldStateModule.types.js | 2 + .../ExpoBrownfieldStateModule.types.js.map | 1 + packages/expo-brownfield/build/index.d.ts | 61 +------- packages/expo-brownfield/build/index.d.ts.map | 2 +- packages/expo-brownfield/build/index.js | 72 +--------- packages/expo-brownfield/build/index.js.map | 2 +- packages/expo-brownfield/build/types.d.ts.map | 1 - packages/expo-brownfield/build/types.js | 2 - packages/expo-brownfield/build/types.js.map | 1 - .../expo-brownfield/expo-module.config.json | 10 +- .../ios/ExpoBrownfieldStateModule.swift | 19 +++ packages/expo-brownfield/package.json | 3 +- .../src/ExpoBrownfieldModule.ts | 96 ++++++++++++- ...types.ts => ExpoBrownfieldModule.types.ts} | 4 +- .../src/ExpoBrownfieldStateModule.ts | 124 ++++++++++++++++ .../src/ExpoBrownfieldStateModule.types.ts | 6 + packages/expo-brownfield/src/index.ts | 85 +---------- 41 files changed, 939 insertions(+), 332 deletions(-) create mode 100644 apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/BrownfieldTestActivity.kt create mode 100644 apps/brownfield-tester/expo-app/src/app/apis/state.tsx create mode 100644 packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/ExpoBrownfieldStateModule.kt create mode 100644 packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/SharedState.kt create mode 100644 packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/State.kt rename packages/expo-brownfield/build/{types.d.ts => ExpoBrownfieldModule.types.d.ts} (78%) create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldModule.types.d.ts.map create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldModule.types.js create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldModule.types.js.map create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldStateModule.d.ts create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldStateModule.d.ts.map create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldStateModule.js create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldStateModule.js.map create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.d.ts create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.d.ts.map create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.js create mode 100644 packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.js.map delete mode 100644 packages/expo-brownfield/build/types.d.ts.map delete mode 100644 packages/expo-brownfield/build/types.js delete mode 100644 packages/expo-brownfield/build/types.js.map create mode 100644 packages/expo-brownfield/ios/ExpoBrownfieldStateModule.swift rename packages/expo-brownfield/src/{types.ts => ExpoBrownfieldModule.types.ts} (83%) create mode 100644 packages/expo-brownfield/src/ExpoBrownfieldStateModule.ts create mode 100644 packages/expo-brownfield/src/ExpoBrownfieldStateModule.types.ts diff --git a/apps/brownfield-tester/android-integrated/app/build.gradle.kts b/apps/brownfield-tester/android-integrated/app/build.gradle.kts index a39bd4c540dfa9..173f47d511ff79 100644 --- a/apps/brownfield-tester/android-integrated/app/build.gradle.kts +++ b/apps/brownfield-tester/android-integrated/app/build.gradle.kts @@ -10,7 +10,7 @@ android { defaultConfig { applicationId = "dev.expo.brownfieldintegratedtester" - minSdk = 24 + minSdk = 26 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/BrownfieldTestActivity.kt b/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/BrownfieldTestActivity.kt new file mode 100644 index 00000000000000..f0e96d534ae7fd --- /dev/null +++ b/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/BrownfieldTestActivity.kt @@ -0,0 +1,106 @@ +package dev.expo.brownfieldintegratedtester + +import android.util.Log +import android.widget.Toast +import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler +import expo.modules.brownfield.BrownfieldMessage +import expo.modules.brownfield.BrownfieldMessaging +import expo.modules.brownfield.BrownfieldState +import expo.modules.brownfield.Removable +import host.exp.exponent.brownfield.BrownfieldActivity +import java.util.Timer +import kotlin.concurrent.timerTask + +open class BrownfieldTestActivity : BrownfieldActivity(), DefaultHardwareBackBtnHandler { + // Listeners + private var messagingListenerId: String? = null + private var stateListener: Removable? = null + + // Other test utils + private var messageTimer: Timer? = null + private var messageCounter = 0 + + fun setupBrownfieldTests() { + // Messaging + messagingListenerId = + BrownfieldMessaging.addListener { message -> + Log.i("BrownfieldTestActivity", "Message from React Native received:") + Log.i("BrownfieldTestActivity", message.toString()) + showToast(message) + } + + // Shared state + stateListener = + BrownfieldState.subscribe("counter") { state: Any? -> + val count = state as? Double + if (count == null) { + Log.i("BrownfieldTestActivity", "Failed to parse state update as a double") + return@subscribe + } + // Return (synchronize) duplicated value to JS + BrownfieldState.set("counter-duplicated", count * 2) + } + + startMessageTimer() + } + + override fun onDestroy() { + super.onDestroy() + // Clean up messaging tests + messagingListenerId?.let { BrownfieldMessaging.removeListener(it) } + stopMessageTimer() + // Clean up state tests + stateListener?.remove() + } + + private fun startMessageTimer() { + messageTimer = + Timer().apply { + schedule( + timerTask { + sendMessage() + setTime() + }, + 0, + 1000, + ) + } + } + + private fun stopMessageTimer() { + messageTimer?.cancel() + messageTimer = null + } + + private fun showToast(message: BrownfieldMessage) { + val sender = message["sender"] as? String + val nested = message["source"] as? Map<*, *> + val platform = nested?.get("platform") as? String + if (sender != null && platform != null) { + Toast.makeText(this, "$platform($sender)", Toast.LENGTH_LONG).show() + } + } + + private fun sendMessage() { + messageCounter++ + val nativeMessage = + mapOf( + "source" to mapOf("platform" to "Android"), + "counter" to messageCounter, + "timestamp" to System.currentTimeMillis(), + "array" to listOf("ab", 'c', false, 1, 2.45), + ) + BrownfieldMessaging.sendMessage(nativeMessage) + } + + private fun setTime() { + val timeString = + java.time.LocalDateTime.now() + .format(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss")) + BrownfieldState.set("time", mapOf("time" to timeString)) + } + + override fun invokeDefaultOnBackPressed() { + TODO("Not yet implemented") + } +} diff --git a/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/MainActivity.kt b/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/MainActivity.kt index a033acd606e93f..212737e3224534 100644 --- a/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/MainActivity.kt +++ b/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/MainActivity.kt @@ -11,33 +11,35 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val rootLayout = LinearLayout(this).apply { - orientation = LinearLayout.VERTICAL - gravity = Gravity.CENTER - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val rootLayout = + LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER + layoutParams = + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) } - val button = Button(this).apply { - text = "Open React Native app" - backgroundTintList = ContextCompat.getColorStateList(context, R.color.purple_500) - id = R.id.openReactNativeButton - setTextColor(Color.WHITE) - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) + val button = + Button(this).apply { + text = "Open React Native app" + backgroundTintList = ContextCompat.getColorStateList(context, R.color.purple_500) + id = R.id.openReactNativeButton + setTextColor(Color.WHITE) + layoutParams = + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ) - setOnClickListener { - startActivity(Intent(context, ReactNativeActivity::class.java)) - } + setOnClickListener { startActivity(Intent(context, ReactNativeActivity::class.java)) } } - rootLayout.addView(button) - setContentView(rootLayout) - } -} \ No newline at end of file + rootLayout.addView(button) + setContentView(rootLayout) + } +} diff --git a/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/ReactNativeActivity.kt b/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/ReactNativeActivity.kt index 160226c3c1396e..83af940f15fab7 100644 --- a/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/ReactNativeActivity.kt +++ b/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/ReactNativeActivity.kt @@ -1,77 +1,14 @@ package dev.expo.brownfieldintegratedtester import android.os.Bundle -import android.widget.Toast import androidx.activity.enableEdgeToEdge -import host.exp.exponent.brownfield.BrownfieldActivity import host.exp.exponent.brownfield.showReactNativeFragment -import expo.modules.brownfield.BrownfieldMessage -import expo.modules.brownfield.BrownfieldMessaging -import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler -import java.util.Timer -import kotlin.concurrent.timerTask -class ReactNativeActivity : BrownfieldActivity(), DefaultHardwareBackBtnHandler { - private var listenerId: String? = null - private var messageTimer: Timer? = null - private var messageCounter = 0 - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - showReactNativeFragment() - - listenerId = - BrownfieldMessaging.addListener { message -> - println("Message from React Native received:") - println(message) - showToast(message) - } - startMessageTimer() - } - - override fun onDestroy() { - super.onDestroy() - listenerId?.let { BrownfieldMessaging.removeListener(it) } - stopMessageTimer() - } - - private fun startMessageTimer() { - messageTimer = Timer() - // Schedule: delay 0ms, repeat every 5000ms (5 seconds) - messageTimer?.schedule(timerTask { - sendMessage() - }, 0, 2500) - } - - private fun stopMessageTimer() { - messageTimer?.cancel() - messageTimer = null - } - - private fun showToast(message: BrownfieldMessage) { - val sender = message["sender"] as? String - val nested = message["source"] as? Map<*, *> - val platform = nested?.get("platform") as? String - if (sender != null && platform != null) { - Toast.makeText(this, "$platform($sender)", Toast.LENGTH_LONG).show() - } - } - - private fun sendMessage() { - messageCounter++ - val nativeMessage = mapOf( - "source" to mapOf( - "platform" to "Android" - ), - "counter" to messageCounter, - "timestamp" to System.currentTimeMillis(), - "array" to listOf("ab", 'c', false, 1, 2.45) - ) - BrownfieldMessaging.sendMessage(nativeMessage) - } - - override fun invokeDefaultOnBackPressed() { - TODO("Not yet implemented") - } -} \ No newline at end of file +class ReactNativeActivity : BrownfieldTestActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + showReactNativeFragment() + setupBrownfieldTests() + } +} diff --git a/apps/brownfield-tester/expo-app/src/app/apis/index.tsx b/apps/brownfield-tester/expo-app/src/app/apis/index.tsx index 96a0f744665fb1..4ccf1ae7f730d2 100644 --- a/apps/brownfield-tester/expo-app/src/app/apis/index.tsx +++ b/apps/brownfield-tester/expo-app/src/app/apis/index.tsx @@ -7,7 +7,7 @@ import { ActionButton } from '@/components'; const Index = () => { const router = useRouter(); - const navigateToScreen = (screen: 'communication' | 'navigation') => { + const navigateToScreen = (screen: 'communication' | 'navigation' | 'state') => { router.navigate(`/apis/${screen}`); }; @@ -29,6 +29,14 @@ const Index = () => { onPress={() => navigateToScreen('navigation')} testID="apis-navigation" /> + navigateToScreen('state')} + testID="apis-state" + /> ); }; diff --git a/apps/brownfield-tester/expo-app/src/app/apis/state.tsx b/apps/brownfield-tester/expo-app/src/app/apis/state.tsx new file mode 100644 index 00000000000000..d488fdb23fb409 --- /dev/null +++ b/apps/brownfield-tester/expo-app/src/app/apis/state.tsx @@ -0,0 +1,136 @@ +import Feather from '@expo/vector-icons/Feather'; +import * as ExpoBrownfield from 'expo-brownfield'; +import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +import { Header } from '@/components'; + +const State = () => { + const [counter, setCounter] = ExpoBrownfield.useSharedState('counter', 0); + const [counterDuplicated] = ExpoBrownfield.useSharedState('counter-duplicated', 0); + const [time] = ExpoBrownfield.useSharedState('time'); + + return ( + +
+ {/* JS to native synchronization */} + JS to native synchronization + + setCounter((prev) => (prev ?? 0) + 1)}> + + + {String(counter)} + setCounter((prev) => (prev ?? 0) - 1)}> + + + + + + + Counter duplicated (in native): {String(counterDuplicated)} + + + {/* Native to JS synchronization */} + Native to JS synchronization + + Time: {time?.time ?? 'N / A'} + + + + ); +}; + +const InputDemo = () => { + const [counter] = ExpoBrownfield.useSharedState('counter'); + return ; +}; + +const BlockDemo = () => { + const [counter] = ExpoBrownfield.useSharedState('counter'); + return Counter: {String(counter)}; +}; + +const TimeInputDemo = () => { + const [time] = ExpoBrownfield.useSharedState('time'); + return ; +}; + +export default State; + +const styles = StyleSheet.create({ + counterDemoContainer: { + paddingHorizontal: 24, + gap: 16, + }, + counterInput: { + fontSize: 18, + fontWeight: 'semibold', + color: 'black', + textAlign: 'center', + borderWidth: 1, + borderRadius: 8, + borderColor: 'gray', + }, + counterBlock: { + fontSize: 18, + fontWeight: 'semibold', + color: 'black', + textAlign: 'center', + borderWidth: 1, + borderRadius: 8, + borderColor: 'orange', + }, + counterButton: { + backgroundColor: '#2563eb', + width: 56, + height: 56, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 28, + }, + counterContainer: { + flexDirection: 'row', + gap: 24, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 32, + }, + counterText: { + fontSize: 64, + fontWeight: 'semibold', + color: 'black', + }, + subTitle: { + fontSize: 18, + fontWeight: 'bold', + color: 'black', + marginTop: 20, + textAlign: 'center', + }, + timeDemoContainer: { + paddingHorizontal: 24, + paddingTop: 16, + gap: 16, + }, + timeInput: { + fontSize: 18, + fontWeight: 'semibold', + color: 'black', + textAlign: 'center', + borderWidth: 1, + borderRadius: 8, + borderColor: 'gray', + }, + timeText: { + fontSize: 28, + fontWeight: 'bold', + color: 'black', + textAlign: 'center', + }, +}); diff --git a/packages/expo-brownfield/CHANGELOG.md b/packages/expo-brownfield/CHANGELOG.md index 04fbab72089924..c28786ebe608c8 100644 --- a/packages/expo-brownfield/CHANGELOG.md +++ b/packages/expo-brownfield/CHANGELOG.md @@ -6,6 +6,8 @@ ### 🎉 New features +- [android] add basic implementation of shared state for android ([#43097](https://github.com/expo/expo/pull/43097) by [@pmleczek](https://github.com/pmleczek)) + ### 🐛 Bug fixes ### 💡 Others diff --git a/packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/ExpoBrownfieldStateModule.kt b/packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/ExpoBrownfieldStateModule.kt new file mode 100644 index 00000000000000..1106918e6b2b65 --- /dev/null +++ b/packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/ExpoBrownfieldStateModule.kt @@ -0,0 +1,26 @@ +package expo.modules.brownfield + +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +class ExpoBrownfieldStateModule : Module() { + override fun definition() = ModuleDefinition { + Name("ExpoBrownfieldStateModule") + + Class(SharedState::class) { + Constructor { SharedState() } + + Function("get") { state: SharedState -> + return@Function state.get() + } + + Function("set") { state: SharedState, value: Any? -> state.set(value) } + } + + Function("getSharedState") { key: String -> + return@Function BrownfieldState.getOrCreate(key) + } + + Function("deleteSharedState") { key: String -> BrownfieldState.delete(key) } + } +} diff --git a/packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/SharedState.kt b/packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/SharedState.kt new file mode 100644 index 00000000000000..aecb59c27c3840 --- /dev/null +++ b/packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/SharedState.kt @@ -0,0 +1,39 @@ +package expo.modules.brownfield + +import expo.modules.kotlin.sharedobjects.SharedObject + +fun interface Removable { + fun remove() +} + +class SharedState : SharedObject() { + private var value: Any? = null + private val listeners = mutableListOf<(Any?) -> Unit>() + + fun get(): Any? { + synchronized(this) { + return value + } + } + + fun set(newValue: Any?) { + val listenersSnapshot: List<(Any?) -> Unit> + synchronized(this) { + value = newValue + listenersSnapshot = listeners.toList() + } + emit("change", mapOf("value" to newValue)) + listenersSnapshot.forEach { it(newValue) } + } + + fun addListener(listener: ((Any?) -> Unit)): Removable { + synchronized(this) { + listeners.add(listener) + } + return Removable { + synchronized(this) { + listeners.remove(listener) + } + } + } +} diff --git a/packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/State.kt b/packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/State.kt new file mode 100644 index 00000000000000..70af02067fe108 --- /dev/null +++ b/packages/expo-brownfield/android/src/main/java/expo/modules/brownfield/State.kt @@ -0,0 +1,39 @@ +package expo.modules.brownfield + +object BrownfieldState { + private val registry = mutableMapOf() + + fun getOrCreate(key: String): SharedState { + synchronized(this) { + return registry.getOrPut(key) { SharedState() } + } + } + + fun get(key: String): Any? { + synchronized(this) { + return registry[key]?.get() + } + } + + fun set(key: String, value: Any?) { + val state: SharedState + synchronized(this) { + state = registry.getOrPut(key) { SharedState() } + } + state.set(value) + } + + fun subscribe(key: String, callback: (Any?) -> Unit): Removable { + val state: SharedState + synchronized(this) { + state = registry.getOrPut(key) { SharedState() } + } + return state.addListener(callback) + } + + fun delete(key: String): Any? { + synchronized(this) { + return registry.remove(key)?.get() + } + } +} diff --git a/packages/expo-brownfield/build/ExpoBrownfieldModule.d.ts b/packages/expo-brownfield/build/ExpoBrownfieldModule.d.ts index 9eb4b73e4db742..9bd6e8c3986f3a 100644 --- a/packages/expo-brownfield/build/ExpoBrownfieldModule.d.ts +++ b/packages/expo-brownfield/build/ExpoBrownfieldModule.d.ts @@ -1,4 +1,60 @@ -import type { ExpoBrownfieldModuleSpec } from './types'; -declare const _default: ExpoBrownfieldModuleSpec; -export default _default; +import type { EventSubscription } from 'expo-modules-core'; +import type { Listener, MessageEvent } from './ExpoBrownfieldModule.types'; +export { EventSubscription }; +export type { MessageEvent }; +/** + * Navigates back to the native part of the app, dismissing the React Native view. + * + * @param animated Whether to animate the transition (iOS only). Defaults to `false`. + * @default false + */ +export declare function popToNative(animated?: boolean): void; +/** + * Enables or disables the native back button behavior. When enabled, pressing the + * back button will navigate back to the native part of the app instead of + * performing the default React Navigation back action. + * + * @param enabled Whether to enable native back button handling. + */ +export declare function setNativeBackEnabled(enabled: boolean): void; +/** + * Adds a listener for messages sent from the native side of the app. + * + * @param listener A callback function that receives message events from native. + * @returns A subscription object that can be used to remove the listener. + * + * @example + * ```ts + * const subscription = addMessageListener((event) => { + * console.log('Received message from native:', event); + * }); + * + * // Later, to remove the listener: + * subscription.remove(); + * ``` + */ +export declare function addMessageListener(listener: Listener): EventSubscription; +/** + * Sends a message to the native side of the app. The message can be received by + * setting up a listener in the native code. + * + * @param message A dictionary containing the message payload to send to native. + */ +export declare function sendMessage(message: Record): void; +/** + * Removes a specific message listener. + * + * @param listener The listener function to remove. + */ +export declare function removeMessageListener(listener: Listener): void; +/** + * Removes all message listeners. + */ +export declare function removeAllMessageListeners(): void; +/** + * Gets the number of registered message listeners. + * + * @returns The number of active message listeners. + */ +export declare function getMessageListenerCount(): number; //# sourceMappingURL=ExpoBrownfieldModule.d.ts.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldModule.d.ts.map b/packages/expo-brownfield/build/ExpoBrownfieldModule.d.ts.map index ad57d5ac6c4ec7..07f69e276f732d 100644 --- a/packages/expo-brownfield/build/ExpoBrownfieldModule.d.ts.map +++ b/packages/expo-brownfield/build/ExpoBrownfieldModule.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ExpoBrownfieldModule.d.ts","sourceRoot":"","sources":["../src/ExpoBrownfieldModule.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;;AAExD,wBAAqF"} \ No newline at end of file +{"version":3,"file":"ExpoBrownfieldModule.d.ts","sourceRoot":"","sources":["../src/ExpoBrownfieldModule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,KAAK,EAEV,QAAQ,EACR,YAAY,EACb,MAAM,8BAA8B,CAAC;AAItC,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,YAAY,EAAE,YAAY,EAAE,CAAC;AAI7B;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,GAAE,OAAe,GAAG,IAAI,CAE3D;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE3D;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,iBAAiB,CAEtF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAE5E;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD"} \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldModule.js b/packages/expo-brownfield/build/ExpoBrownfieldModule.js index 999f50fe68d3a1..47c8f10ef7279c 100644 --- a/packages/expo-brownfield/build/ExpoBrownfieldModule.js +++ b/packages/expo-brownfield/build/ExpoBrownfieldModule.js @@ -1,3 +1,76 @@ import { requireNativeModule } from 'expo'; -export default requireNativeModule('ExpoBrownfieldModule'); +const ExpoBrownfieldModule = requireNativeModule('ExpoBrownfieldModule'); +// SECTION: Navigation API +/** + * Navigates back to the native part of the app, dismissing the React Native view. + * + * @param animated Whether to animate the transition (iOS only). Defaults to `false`. + * @default false + */ +export function popToNative(animated = false) { + ExpoBrownfieldModule.popToNative(animated); +} +/** + * Enables or disables the native back button behavior. When enabled, pressing the + * back button will navigate back to the native part of the app instead of + * performing the default React Navigation back action. + * + * @param enabled Whether to enable native back button handling. + */ +export function setNativeBackEnabled(enabled) { + ExpoBrownfieldModule.setNativeBackEnabled(enabled); +} +// END SECTION: Navigation API +// SECTION: Messaging API +/** + * Adds a listener for messages sent from the native side of the app. + * + * @param listener A callback function that receives message events from native. + * @returns A subscription object that can be used to remove the listener. + * + * @example + * ```ts + * const subscription = addMessageListener((event) => { + * console.log('Received message from native:', event); + * }); + * + * // Later, to remove the listener: + * subscription.remove(); + * ``` + */ +export function addMessageListener(listener) { + return ExpoBrownfieldModule.addListener('onMessage', listener); +} +/** + * Sends a message to the native side of the app. The message can be received by + * setting up a listener in the native code. + * + * @param message A dictionary containing the message payload to send to native. + */ +export function sendMessage(message) { + ExpoBrownfieldModule.sendMessage(message); +} +/** + * Removes a specific message listener. + * + * @param listener The listener function to remove. + */ +export function removeMessageListener(listener) { + ExpoBrownfieldModule.removeListener('onMessage', listener); +} +/** + * Removes all message listeners. + */ +export function removeAllMessageListeners() { + ExpoBrownfieldModule.removeAllListeners('onMessage'); +} +/** + * Gets the number of registered message listeners. + * + * @returns The number of active message listeners. + */ +export function getMessageListenerCount() { + return ExpoBrownfieldModule.listenerCount('onMessage'); +} +// END SECTION: Messaging API //# sourceMappingURL=ExpoBrownfieldModule.js.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldModule.js.map b/packages/expo-brownfield/build/ExpoBrownfieldModule.js.map index 1f48c234f443d6..55db482fa40843 100644 --- a/packages/expo-brownfield/build/ExpoBrownfieldModule.js.map +++ b/packages/expo-brownfield/build/ExpoBrownfieldModule.js.map @@ -1 +1 @@ -{"version":3,"file":"ExpoBrownfieldModule.js","sourceRoot":"","sources":["../src/ExpoBrownfieldModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAI3C,eAAe,mBAAmB,CAA2B,sBAAsB,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo';\n\nimport type { ExpoBrownfieldModuleSpec } from './types';\n\nexport default requireNativeModule('ExpoBrownfieldModule');\n"]} \ No newline at end of file +{"version":3,"file":"ExpoBrownfieldModule.js","sourceRoot":"","sources":["../src/ExpoBrownfieldModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAS3C,MAAM,oBAAoB,GAAG,mBAAmB,CAA2B,sBAAsB,CAAC,CAAC;AAKnG,0BAA0B;AAE1B;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,WAAoB,KAAK;IACnD,oBAAoB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,oBAAoB,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;AACrD,CAAC;AAED,8BAA8B;AAE9B,yBAAyB;AAEzB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgC;IACjE,OAAO,oBAAoB,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AACjE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA4B;IACtD,oBAAoB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgC;IACpE,oBAAoB,CAAC,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB;IACvC,oBAAoB,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB;IACrC,OAAO,oBAAoB,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;AACzD,CAAC;AAED,6BAA6B","sourcesContent":["import { requireNativeModule } from 'expo';\nimport type { EventSubscription } from 'expo-modules-core';\n\nimport type {\n ExpoBrownfieldModuleSpec,\n Listener,\n MessageEvent,\n} from './ExpoBrownfieldModule.types';\n\nconst ExpoBrownfieldModule = requireNativeModule('ExpoBrownfieldModule');\n\nexport { EventSubscription };\nexport type { MessageEvent };\n\n// SECTION: Navigation API\n\n/**\n * Navigates back to the native part of the app, dismissing the React Native view.\n *\n * @param animated Whether to animate the transition (iOS only). Defaults to `false`.\n * @default false\n */\nexport function popToNative(animated: boolean = false): void {\n ExpoBrownfieldModule.popToNative(animated);\n}\n\n/**\n * Enables or disables the native back button behavior. When enabled, pressing the\n * back button will navigate back to the native part of the app instead of\n * performing the default React Navigation back action.\n *\n * @param enabled Whether to enable native back button handling.\n */\nexport function setNativeBackEnabled(enabled: boolean): void {\n ExpoBrownfieldModule.setNativeBackEnabled(enabled);\n}\n\n// END SECTION: Navigation API\n\n// SECTION: Messaging API\n\n/**\n * Adds a listener for messages sent from the native side of the app.\n *\n * @param listener A callback function that receives message events from native.\n * @returns A subscription object that can be used to remove the listener.\n *\n * @example\n * ```ts\n * const subscription = addMessageListener((event) => {\n * console.log('Received message from native:', event);\n * });\n *\n * // Later, to remove the listener:\n * subscription.remove();\n * ```\n */\nexport function addMessageListener(listener: Listener): EventSubscription {\n return ExpoBrownfieldModule.addListener('onMessage', listener);\n}\n\n/**\n * Sends a message to the native side of the app. The message can be received by\n * setting up a listener in the native code.\n *\n * @param message A dictionary containing the message payload to send to native.\n */\nexport function sendMessage(message: Record): void {\n ExpoBrownfieldModule.sendMessage(message);\n}\n\n/**\n * Removes a specific message listener.\n *\n * @param listener The listener function to remove.\n */\nexport function removeMessageListener(listener: Listener): void {\n ExpoBrownfieldModule.removeListener('onMessage', listener);\n}\n\n/**\n * Removes all message listeners.\n */\nexport function removeAllMessageListeners(): void {\n ExpoBrownfieldModule.removeAllListeners('onMessage');\n}\n\n/**\n * Gets the number of registered message listeners.\n *\n * @returns The number of active message listeners.\n */\nexport function getMessageListenerCount(): number {\n return ExpoBrownfieldModule.listenerCount('onMessage');\n}\n\n// END SECTION: Messaging API\n"]} \ No newline at end of file diff --git a/packages/expo-brownfield/build/types.d.ts b/packages/expo-brownfield/build/ExpoBrownfieldModule.types.d.ts similarity index 78% rename from packages/expo-brownfield/build/types.d.ts rename to packages/expo-brownfield/build/ExpoBrownfieldModule.types.d.ts index 8ef5e7581c7b83..a9119858be3ae2 100644 --- a/packages/expo-brownfield/build/types.d.ts +++ b/packages/expo-brownfield/build/ExpoBrownfieldModule.types.d.ts @@ -1,12 +1,12 @@ import type { NativeModule } from 'expo'; export type MessageEvent = Record; export type Listener = (event: E) => void; -export type ExpoBrownfieldModuleEvents = { +export type Events = { onMessage: (event: MessageEvent) => void; }; -export declare class ExpoBrownfieldModuleSpec extends NativeModule { +export declare class ExpoBrownfieldModuleSpec extends NativeModule { popToNative(animated: boolean): void; setNativeBackEnabled(enabled: boolean): void; sendMessage(message: Record): void; } -//# sourceMappingURL=types.d.ts.map \ No newline at end of file +//# sourceMappingURL=ExpoBrownfieldModule.types.d.ts.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldModule.types.d.ts.map b/packages/expo-brownfield/build/ExpoBrownfieldModule.types.d.ts.map new file mode 100644 index 00000000000000..4426fdbfe5bff0 --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldModule.types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"ExpoBrownfieldModule.types.d.ts","sourceRoot":"","sources":["../src/ExpoBrownfieldModule.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/C,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAE7C,MAAM,MAAM,MAAM,GAAG;IACnB,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,wBAAyB,SAAQ,YAAY,CAAC,MAAM,CAAC;IACxE,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IACpC,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAC5C,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;CAChD"} \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldModule.types.js b/packages/expo-brownfield/build/ExpoBrownfieldModule.types.js new file mode 100644 index 00000000000000..c1f7480b0af009 --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldModule.types.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=ExpoBrownfieldModule.types.js.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldModule.types.js.map b/packages/expo-brownfield/build/ExpoBrownfieldModule.types.js.map new file mode 100644 index 00000000000000..4f35e06734b223 --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldModule.types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ExpoBrownfieldModule.types.js","sourceRoot":"","sources":["../src/ExpoBrownfieldModule.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { NativeModule } from 'expo';\n\nexport type MessageEvent = Record;\n\nexport type Listener = (event: E) => void;\n\nexport type Events = {\n onMessage: (event: MessageEvent) => void;\n};\n\nexport declare class ExpoBrownfieldModuleSpec extends NativeModule {\n popToNative(animated: boolean): void;\n setNativeBackEnabled(enabled: boolean): void;\n sendMessage(message: Record): void;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldStateModule.d.ts b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.d.ts new file mode 100644 index 00000000000000..ede35da8b6abe0 --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.d.ts @@ -0,0 +1,38 @@ +import type { EventSubscription } from 'expo-modules-core'; +/** + * Gets the value of shared state for a given key. + * + * @param key The key to get the value for. + */ +export declare function getSharedStateValue(key: string): T | undefined; +/** + * Sets the value of shared state for a given key. + * + * @param key The key to set the value for. + * @param value The value to be set. + */ +export declare function setSharedStateValue(key: string, value: T): void; +/** + * Deletes the shared state for a given key. + * + * @param key The key to delete the shared state for. + */ +export declare function deleteSharedState(key: string): void; +/** + * Adds a listener for changes to the shared state for a given key. + * + * @param key The key to add the listener for. + * @param callback The callback to be called when the shared state changes. + * @returns A subscription object that can be used to remove the listener. + */ +export declare function addSharedStateListener(key: string, callback: (value: T | undefined) => void): EventSubscription; +/** + * Hook to observe and set the value of shared state for a given key. + * Provides a synchronous API similar to `useState`. + * + * @param key The key to get the value for. + * @param initialValue The initial value to be used if the shared state is not set. + * @returns A tuple containing the value and a function to set the value. + */ +export declare function useSharedState(key: string, initialValue?: T): [T | undefined, (value: T | ((prev: T | undefined) => T)) => void]; +//# sourceMappingURL=ExpoBrownfieldStateModule.d.ts.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldStateModule.d.ts.map b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.d.ts.map new file mode 100644 index 00000000000000..32f2bf99cd3409 --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"ExpoBrownfieldStateModule.d.ts","sourceRoot":"","sources":["../src/ExpoBrownfieldStateModule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAoB3D;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAIvE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAGxE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGnD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,GAAG,GAAG,EAC5C,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI,GACvC,iBAAiB,CAUnB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,GAAG,GAAG,EACpC,GAAG,EAAE,MAAM,EACX,YAAY,CAAC,EAAE,CAAC,GACf,CAAC,CAAC,GAAG,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAmCpE"} \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldStateModule.js b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.js new file mode 100644 index 00000000000000..51748b596e060f --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.js @@ -0,0 +1,91 @@ +import { requireNativeModule } from 'expo'; +import { useEffect, useState } from 'react'; +const ExpoBrownfieldStateModule = requireNativeModule('ExpoBrownfieldStateModule'); +const sharedObjectCache = new Map(); +// SECTION: Shared State API +function getSharedObject(key) { + if (!sharedObjectCache.has(key)) { + sharedObjectCache.set(key, ExpoBrownfieldStateModule.getSharedState(key)); + } + return sharedObjectCache.get(key); +} +/** + * Gets the value of shared state for a given key. + * + * @param key The key to get the value for. + */ +export function getSharedStateValue(key) { + const state = getSharedObject(key); + const value = state?.get(); + return value === null ? undefined : value; +} +/** + * Sets the value of shared state for a given key. + * + * @param key The key to set the value for. + * @param value The value to be set. + */ +export function setSharedStateValue(key, value) { + const state = getSharedObject(key); + state.set(value); +} +/** + * Deletes the shared state for a given key. + * + * @param key The key to delete the shared state for. + */ +export function deleteSharedState(key) { + ExpoBrownfieldStateModule.deleteSharedState(key); + sharedObjectCache.delete(key); +} +/** + * Adds a listener for changes to the shared state for a given key. + * + * @param key The key to add the listener for. + * @param callback The callback to be called when the shared state changes. + * @returns A subscription object that can be used to remove the listener. + */ +export function addSharedStateListener(key, callback) { + const state = getSharedObject(key); + const subscription = state.addListener('change', (event) => { + callback(event); + }); + return { + remove: () => subscription.remove(), + }; +} +/** + * Hook to observe and set the value of shared state for a given key. + * Provides a synchronous API similar to `useState`. + * + * @param key The key to get the value for. + * @param initialValue The initial value to be used if the shared state is not set. + * @returns A tuple containing the value and a function to set the value. + */ +export function useSharedState(key, initialValue) { + const state = getSharedObject(key); + const [value, setValue] = useState(() => { + const currentValue = state.get(); + if (currentValue === null || currentValue === undefined) { + if (initialValue !== undefined) { + state.set(initialValue); + return initialValue; + } + return undefined; + } + return currentValue; + }); + useEffect(() => { + const subscription = state.addListener('change', (event) => { + setValue(event?.['value']); + }); + return () => subscription.remove(); + }, [state]); + const setSharedValue = (newValue) => { + const valueToSet = typeof newValue === 'function' ? newValue(value) : newValue; + state.set(valueToSet); + }; + return [value, setSharedValue]; +} +// END SECTION: Shared State API +//# sourceMappingURL=ExpoBrownfieldStateModule.js.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldStateModule.js.map b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.js.map new file mode 100644 index 00000000000000..b60d005206352d --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ExpoBrownfieldStateModule.js","sourceRoot":"","sources":["../src/ExpoBrownfieldStateModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAI5C,MAAM,yBAAyB,GAAG,mBAAmB,CACnD,2BAA2B,CAC5B,CAAC;AAEF,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAe,CAAC;AAEjD,4BAA4B;AAE5B,SAAS,eAAe,CAAC,GAAW;IAClC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAChC,iBAAiB,CAAC,GAAG,CAAC,GAAG,EAAE,yBAAyB,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAU,GAAW;IACtD,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,KAAK,EAAE,GAAG,EAAE,CAAC;IAC3B,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,KAAW,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAU,GAAW,EAAE,KAAQ;IAChE,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACnC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,yBAAyB,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACjD,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,GAAW,EACX,QAAwC;IAExC,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAEnC,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,KAAoB,EAAE,EAAE;QACxE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE;KACpC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAW,EACX,YAAgB;IAEhB,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAEnC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,GAAG,EAAE;QACrD,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACjC,IAAI,YAAY,KAAK,IAAI,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YACxD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/B,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;gBACxB,OAAO,YAAY,CAAC;YACtB,CAAC;YAED,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,YAAiB,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,CACpC,QAAQ,EACR,CAAC,KAAgD,EAAE,EAAE;YACnD,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7B,CAAC,CACF,CAAC;QAEF,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;IACrC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,MAAM,cAAc,GAAG,CAAC,QAA0C,EAAE,EAAE;QACpE,MAAM,UAAU,GACd,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAE,QAAuC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC9F,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,OAAO,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;AACjC,CAAC;AAED,gCAAgC","sourcesContent":["import { requireNativeModule } from 'expo';\nimport type { EventSubscription } from 'expo-modules-core';\nimport { useEffect, useState } from 'react';\n\nimport type { ExpoBrownfieldStateModuleSpec } from './ExpoBrownfieldStateModule.types';\n\nconst ExpoBrownfieldStateModule = requireNativeModule(\n 'ExpoBrownfieldStateModule'\n);\n\nconst sharedObjectCache = new Map();\n\n// SECTION: Shared State API\n\nfunction getSharedObject(key: string): any {\n if (!sharedObjectCache.has(key)) {\n sharedObjectCache.set(key, ExpoBrownfieldStateModule.getSharedState(key));\n }\n return sharedObjectCache.get(key);\n}\n\n/**\n * Gets the value of shared state for a given key.\n *\n * @param key The key to get the value for.\n */\nexport function getSharedStateValue(key: string): T | undefined {\n const state = getSharedObject(key);\n const value = state?.get();\n return value === null ? undefined : (value as T);\n}\n\n/**\n * Sets the value of shared state for a given key.\n *\n * @param key The key to set the value for.\n * @param value The value to be set.\n */\nexport function setSharedStateValue(key: string, value: T): void {\n const state = getSharedObject(key);\n state.set(value);\n}\n\n/**\n * Deletes the shared state for a given key.\n *\n * @param key The key to delete the shared state for.\n */\nexport function deleteSharedState(key: string): void {\n ExpoBrownfieldStateModule.deleteSharedState(key);\n sharedObjectCache.delete(key);\n}\n\n/**\n * Adds a listener for changes to the shared state for a given key.\n *\n * @param key The key to add the listener for.\n * @param callback The callback to be called when the shared state changes.\n * @returns A subscription object that can be used to remove the listener.\n */\nexport function addSharedStateListener(\n key: string,\n callback: (value: T | undefined) => void\n): EventSubscription {\n const state = getSharedObject(key);\n\n const subscription = state.addListener('change', (event: T | undefined) => {\n callback(event);\n });\n\n return {\n remove: () => subscription.remove(),\n };\n}\n\n/**\n * Hook to observe and set the value of shared state for a given key.\n * Provides a synchronous API similar to `useState`.\n *\n * @param key The key to get the value for.\n * @param initialValue The initial value to be used if the shared state is not set.\n * @returns A tuple containing the value and a function to set the value.\n */\nexport function useSharedState(\n key: string,\n initialValue?: T\n): [T | undefined, (value: T | ((prev: T | undefined) => T)) => void] {\n const state = getSharedObject(key);\n\n const [value, setValue] = useState(() => {\n const currentValue = state.get();\n if (currentValue === null || currentValue === undefined) {\n if (initialValue !== undefined) {\n state.set(initialValue);\n return initialValue;\n }\n\n return undefined;\n }\n\n return currentValue as T;\n });\n\n useEffect(() => {\n const subscription = state.addListener(\n 'change',\n (event: Record | undefined) => {\n setValue(event?.['value']);\n }\n );\n\n return () => subscription.remove();\n }, [state]);\n\n const setSharedValue = (newValue: T | ((prev: T | undefined) => T)) => {\n const valueToSet =\n typeof newValue === 'function' ? (newValue as (prev: T | undefined) => T)(value) : newValue;\n state.set(valueToSet);\n };\n\n return [value, setSharedValue];\n}\n\n// END SECTION: Shared State API\n"]} \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.d.ts b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.d.ts new file mode 100644 index 00000000000000..555dda53038102 --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.d.ts @@ -0,0 +1,6 @@ +import type { NativeModule } from 'expo'; +export declare class ExpoBrownfieldStateModuleSpec extends NativeModule { + getSharedState(key: string): any; + deleteSharedState(key: string): void; +} +//# sourceMappingURL=ExpoBrownfieldStateModule.types.d.ts.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.d.ts.map b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.d.ts.map new file mode 100644 index 00000000000000..4c9e71c50eb548 --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"ExpoBrownfieldStateModule.types.d.ts","sourceRoot":"","sources":["../src/ExpoBrownfieldStateModule.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,MAAM,CAAC,OAAO,OAAO,6BAA8B,SAAQ,YAAY;IACrE,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG;IAChC,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CACrC"} \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.js b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.js new file mode 100644 index 00000000000000..443052555855f6 --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=ExpoBrownfieldStateModule.types.js.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.js.map b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.js.map new file mode 100644 index 00000000000000..283c5e67b4f065 --- /dev/null +++ b/packages/expo-brownfield/build/ExpoBrownfieldStateModule.types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ExpoBrownfieldStateModule.types.js","sourceRoot":"","sources":["../src/ExpoBrownfieldStateModule.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { NativeModule } from 'expo';\n\nexport declare class ExpoBrownfieldStateModuleSpec extends NativeModule {\n getSharedState(key: string): any;\n deleteSharedState(key: string): void;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-brownfield/build/index.d.ts b/packages/expo-brownfield/build/index.d.ts index 2ac27acefb2c36..de3b5e82200106 100644 --- a/packages/expo-brownfield/build/index.d.ts +++ b/packages/expo-brownfield/build/index.d.ts @@ -1,60 +1,3 @@ -import type { EventSubscription } from 'expo-modules-core'; -import type { Listener, MessageEvent } from './types'; -export { EventSubscription }; -export type { MessageEvent }; -/** - * Navigates back to the native part of the app, dismissing the React Native view. - * - * @param animated Whether to animate the transition (iOS only). Defaults to `false`. - * @default false - */ -export declare function popToNative(animated?: boolean): void; -/** - * Enables or disables the native back button behavior. When enabled, pressing the - * back button will navigate back to the native part of the app instead of - * performing the default React Navigation back action. - * - * @param enabled Whether to enable native back button handling. - */ -export declare function setNativeBackEnabled(enabled: boolean): void; -/** - * Adds a listener for messages sent from the native side of the app. - * - * @param listener A callback function that receives message events from native. - * @returns A subscription object that can be used to remove the listener. - * - * @example - * ```ts - * const subscription = addMessageListener((event) => { - * console.log('Received message from native:', event); - * }); - * - * // Later, to remove the listener: - * subscription.remove(); - * ``` - */ -export declare function addMessageListener(listener: Listener): EventSubscription; -/** - * Sends a message to the native side of the app. The message can be received by - * setting up a listener in the native code. - * - * @param message A dictionary containing the message payload to send to native. - */ -export declare function sendMessage(message: Record): void; -/** - * Removes a specific message listener. - * - * @param listener The listener function to remove. - */ -export declare function removeMessageListener(listener: Listener): void; -/** - * Removes all message listeners. - */ -export declare function removeAllMessageListeners(): void; -/** - * Gets the number of registered message listeners. - * - * @returns The number of active message listeners. - */ -export declare function getMessageListenerCount(): number; +export * from './ExpoBrownfieldModule'; +export * from './ExpoBrownfieldStateModule'; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/index.d.ts.map b/packages/expo-brownfield/build/index.d.ts.map index 9669b8a85226c7..708929d75dc5ce 100644 --- a/packages/expo-brownfield/build/index.d.ts.map +++ b/packages/expo-brownfield/build/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,YAAY,EAAE,YAAY,EAAE,CAAC;AAE7B;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,GAAE,OAAe,GAAG,IAAI,CAE3D;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,iBAAiB,CAEtF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAE5E;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC"} \ No newline at end of file diff --git a/packages/expo-brownfield/build/index.js b/packages/expo-brownfield/build/index.js index df86a5771f57b4..b5adc1c2f79cb4 100644 --- a/packages/expo-brownfield/build/index.js +++ b/packages/expo-brownfield/build/index.js @@ -1,71 +1,3 @@ -import ExpoBrownfieldModule from './ExpoBrownfieldModule'; -/** - * Navigates back to the native part of the app, dismissing the React Native view. - * - * @param animated Whether to animate the transition (iOS only). Defaults to `false`. - * @default false - */ -export function popToNative(animated = false) { - ExpoBrownfieldModule.popToNative(animated); -} -/** - * Enables or disables the native back button behavior. When enabled, pressing the - * back button will navigate back to the native part of the app instead of - * performing the default React Navigation back action. - * - * @param enabled Whether to enable native back button handling. - */ -export function setNativeBackEnabled(enabled) { - ExpoBrownfieldModule.setNativeBackEnabled(enabled); -} -/** - * Adds a listener for messages sent from the native side of the app. - * - * @param listener A callback function that receives message events from native. - * @returns A subscription object that can be used to remove the listener. - * - * @example - * ```ts - * const subscription = addMessageListener((event) => { - * console.log('Received message from native:', event); - * }); - * - * // Later, to remove the listener: - * subscription.remove(); - * ``` - */ -export function addMessageListener(listener) { - return ExpoBrownfieldModule.addListener('onMessage', listener); -} -/** - * Sends a message to the native side of the app. The message can be received by - * setting up a listener in the native code. - * - * @param message A dictionary containing the message payload to send to native. - */ -export function sendMessage(message) { - ExpoBrownfieldModule.sendMessage(message); -} -/** - * Removes a specific message listener. - * - * @param listener The listener function to remove. - */ -export function removeMessageListener(listener) { - ExpoBrownfieldModule.removeListener('onMessage', listener); -} -/** - * Removes all message listeners. - */ -export function removeAllMessageListeners() { - ExpoBrownfieldModule.removeAllListeners('onMessage'); -} -/** - * Gets the number of registered message listeners. - * - * @returns The number of active message listeners. - */ -export function getMessageListenerCount() { - return ExpoBrownfieldModule.listenerCount('onMessage'); -} +export * from './ExpoBrownfieldModule'; +export * from './ExpoBrownfieldStateModule'; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/index.js.map b/packages/expo-brownfield/build/index.js.map index 8a76ef6c2da619..c7669be74e4f58 100644 --- a/packages/expo-brownfield/build/index.js.map +++ b/packages/expo-brownfield/build/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAM1D;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,WAAoB,KAAK;IACnD,oBAAoB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,oBAAoB,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgC;IACjE,OAAO,oBAAoB,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AACjE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA4B;IACtD,oBAAoB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgC;IACpE,oBAAoB,CAAC,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB;IACvC,oBAAoB,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB;IACrC,OAAO,oBAAoB,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;AACzD,CAAC","sourcesContent":["import type { EventSubscription } from 'expo-modules-core';\n\nimport ExpoBrownfieldModule from './ExpoBrownfieldModule';\nimport type { Listener, MessageEvent } from './types';\n\nexport { EventSubscription };\nexport type { MessageEvent };\n\n/**\n * Navigates back to the native part of the app, dismissing the React Native view.\n *\n * @param animated Whether to animate the transition (iOS only). Defaults to `false`.\n * @default false\n */\nexport function popToNative(animated: boolean = false): void {\n ExpoBrownfieldModule.popToNative(animated);\n}\n\n/**\n * Enables or disables the native back button behavior. When enabled, pressing the\n * back button will navigate back to the native part of the app instead of\n * performing the default React Navigation back action.\n *\n * @param enabled Whether to enable native back button handling.\n */\nexport function setNativeBackEnabled(enabled: boolean): void {\n ExpoBrownfieldModule.setNativeBackEnabled(enabled);\n}\n\n/**\n * Adds a listener for messages sent from the native side of the app.\n *\n * @param listener A callback function that receives message events from native.\n * @returns A subscription object that can be used to remove the listener.\n *\n * @example\n * ```ts\n * const subscription = addMessageListener((event) => {\n * console.log('Received message from native:', event);\n * });\n *\n * // Later, to remove the listener:\n * subscription.remove();\n * ```\n */\nexport function addMessageListener(listener: Listener): EventSubscription {\n return ExpoBrownfieldModule.addListener('onMessage', listener);\n}\n\n/**\n * Sends a message to the native side of the app. The message can be received by\n * setting up a listener in the native code.\n *\n * @param message A dictionary containing the message payload to send to native.\n */\nexport function sendMessage(message: Record): void {\n ExpoBrownfieldModule.sendMessage(message);\n}\n\n/**\n * Removes a specific message listener.\n *\n * @param listener The listener function to remove.\n */\nexport function removeMessageListener(listener: Listener): void {\n ExpoBrownfieldModule.removeListener('onMessage', listener);\n}\n\n/**\n * Removes all message listeners.\n */\nexport function removeAllMessageListeners(): void {\n ExpoBrownfieldModule.removeAllListeners('onMessage');\n}\n\n/**\n * Gets the number of registered message listeners.\n *\n * @returns The number of active message listeners.\n */\nexport function getMessageListenerCount(): number {\n return ExpoBrownfieldModule.listenerCount('onMessage');\n}\n"]} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC","sourcesContent":["export * from './ExpoBrownfieldModule';\nexport * from './ExpoBrownfieldStateModule';\n"]} \ No newline at end of file diff --git a/packages/expo-brownfield/build/types.d.ts.map b/packages/expo-brownfield/build/types.d.ts.map deleted file mode 100644 index da0b63d9d39a85..00000000000000 --- a/packages/expo-brownfield/build/types.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/C,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAE7C,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,wBAAyB,SAAQ,YAAY,CAAC,0BAA0B,CAAC;IAC5F,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IACpC,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAC5C,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;CAChD"} \ No newline at end of file diff --git a/packages/expo-brownfield/build/types.js b/packages/expo-brownfield/build/types.js deleted file mode 100644 index 718fd38ae40c67..00000000000000 --- a/packages/expo-brownfield/build/types.js +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/packages/expo-brownfield/build/types.js.map b/packages/expo-brownfield/build/types.js.map deleted file mode 100644 index 8a222fa592913a..00000000000000 --- a/packages/expo-brownfield/build/types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { NativeModule } from 'expo';\n\nexport type MessageEvent = Record;\n\nexport type Listener = (event: E) => void;\n\nexport type ExpoBrownfieldModuleEvents = {\n onMessage: (event: MessageEvent) => void;\n};\n\nexport declare class ExpoBrownfieldModuleSpec extends NativeModule {\n popToNative(animated: boolean): void;\n setNativeBackEnabled(enabled: boolean): void;\n sendMessage(message: Record): void;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-brownfield/expo-module.config.json b/packages/expo-brownfield/expo-module.config.json index efb65fa1a02d48..1ffe01a7cb748c 100644 --- a/packages/expo-brownfield/expo-module.config.json +++ b/packages/expo-brownfield/expo-module.config.json @@ -1,9 +1,15 @@ { "platforms": ["apple", "android", "web"], "apple": { - "modules": ["ExpoBrownfieldModule"] + "modules": [ + "ExpoBrownfieldModule", + "ExpoBrownfieldStateModule" + ] }, "android": { - "modules": ["expo.modules.brownfield.ExpoBrownfieldModule"] + "modules": [ + "expo.modules.brownfield.ExpoBrownfieldModule", + "expo.modules.brownfield.ExpoBrownfieldStateModule" + ] } } diff --git a/packages/expo-brownfield/ios/ExpoBrownfieldStateModule.swift b/packages/expo-brownfield/ios/ExpoBrownfieldStateModule.swift new file mode 100644 index 00000000000000..b62bb3f458c7bb --- /dev/null +++ b/packages/expo-brownfield/ios/ExpoBrownfieldStateModule.swift @@ -0,0 +1,19 @@ +import ExpoModulesCore + +// MARK: - ExpoBrownfieldStateModule + +public class ExpoBrownfieldStateModule: Module { + private var stores: [String: Any] = [:] + + public func definition() -> ModuleDefinition { + Name("ExpoBrownfieldStateModule") + + Function("getSharedState") { (key: String) -> Any? in + return nil + } + + Function("deleteSharedState") { (key: String) -> Void in + self.stores.removeValue(forKey: key) + } + } +} diff --git a/packages/expo-brownfield/package.json b/packages/expo-brownfield/package.json index 8c8b10bd712404..c80098db2baafc 100644 --- a/packages/expo-brownfield/package.json +++ b/packages/expo-brownfield/package.json @@ -64,6 +64,7 @@ "expo-module-scripts": "^55.0.2" }, "peerDependencies": { - "expo": "*" + "expo": "*", + "react": "*" } } diff --git a/packages/expo-brownfield/src/ExpoBrownfieldModule.ts b/packages/expo-brownfield/src/ExpoBrownfieldModule.ts index 1a3762e1449e42..66c5b8321cf7be 100644 --- a/packages/expo-brownfield/src/ExpoBrownfieldModule.ts +++ b/packages/expo-brownfield/src/ExpoBrownfieldModule.ts @@ -1,5 +1,97 @@ import { requireNativeModule } from 'expo'; +import type { EventSubscription } from 'expo-modules-core'; -import type { ExpoBrownfieldModuleSpec } from './types'; +import type { + ExpoBrownfieldModuleSpec, + Listener, + MessageEvent, +} from './ExpoBrownfieldModule.types'; -export default requireNativeModule('ExpoBrownfieldModule'); +const ExpoBrownfieldModule = requireNativeModule('ExpoBrownfieldModule'); + +export { EventSubscription }; +export type { MessageEvent }; + +// SECTION: Navigation API + +/** + * Navigates back to the native part of the app, dismissing the React Native view. + * + * @param animated Whether to animate the transition (iOS only). Defaults to `false`. + * @default false + */ +export function popToNative(animated: boolean = false): void { + ExpoBrownfieldModule.popToNative(animated); +} + +/** + * Enables or disables the native back button behavior. When enabled, pressing the + * back button will navigate back to the native part of the app instead of + * performing the default React Navigation back action. + * + * @param enabled Whether to enable native back button handling. + */ +export function setNativeBackEnabled(enabled: boolean): void { + ExpoBrownfieldModule.setNativeBackEnabled(enabled); +} + +// END SECTION: Navigation API + +// SECTION: Messaging API + +/** + * Adds a listener for messages sent from the native side of the app. + * + * @param listener A callback function that receives message events from native. + * @returns A subscription object that can be used to remove the listener. + * + * @example + * ```ts + * const subscription = addMessageListener((event) => { + * console.log('Received message from native:', event); + * }); + * + * // Later, to remove the listener: + * subscription.remove(); + * ``` + */ +export function addMessageListener(listener: Listener): EventSubscription { + return ExpoBrownfieldModule.addListener('onMessage', listener); +} + +/** + * Sends a message to the native side of the app. The message can be received by + * setting up a listener in the native code. + * + * @param message A dictionary containing the message payload to send to native. + */ +export function sendMessage(message: Record): void { + ExpoBrownfieldModule.sendMessage(message); +} + +/** + * Removes a specific message listener. + * + * @param listener The listener function to remove. + */ +export function removeMessageListener(listener: Listener): void { + ExpoBrownfieldModule.removeListener('onMessage', listener); +} + +/** + * Removes all message listeners. + */ +export function removeAllMessageListeners(): void { + ExpoBrownfieldModule.removeAllListeners('onMessage'); +} + +/** + * Gets the number of registered message listeners. + * + * @returns The number of active message listeners. + */ +export function getMessageListenerCount(): number { + return ExpoBrownfieldModule.listenerCount('onMessage'); +} + +// END SECTION: Messaging API diff --git a/packages/expo-brownfield/src/types.ts b/packages/expo-brownfield/src/ExpoBrownfieldModule.types.ts similarity index 83% rename from packages/expo-brownfield/src/types.ts rename to packages/expo-brownfield/src/ExpoBrownfieldModule.types.ts index 0e10134dd858cf..da0985b876686d 100644 --- a/packages/expo-brownfield/src/types.ts +++ b/packages/expo-brownfield/src/ExpoBrownfieldModule.types.ts @@ -4,11 +4,11 @@ export type MessageEvent = Record; export type Listener = (event: E) => void; -export type ExpoBrownfieldModuleEvents = { +export type Events = { onMessage: (event: MessageEvent) => void; }; -export declare class ExpoBrownfieldModuleSpec extends NativeModule { +export declare class ExpoBrownfieldModuleSpec extends NativeModule { popToNative(animated: boolean): void; setNativeBackEnabled(enabled: boolean): void; sendMessage(message: Record): void; diff --git a/packages/expo-brownfield/src/ExpoBrownfieldStateModule.ts b/packages/expo-brownfield/src/ExpoBrownfieldStateModule.ts new file mode 100644 index 00000000000000..8f1f4e4849fcf2 --- /dev/null +++ b/packages/expo-brownfield/src/ExpoBrownfieldStateModule.ts @@ -0,0 +1,124 @@ +import { requireNativeModule } from 'expo'; +import type { EventSubscription } from 'expo-modules-core'; +import { useEffect, useState } from 'react'; + +import type { ExpoBrownfieldStateModuleSpec } from './ExpoBrownfieldStateModule.types'; + +const ExpoBrownfieldStateModule = requireNativeModule( + 'ExpoBrownfieldStateModule' +); + +const sharedObjectCache = new Map(); + +// SECTION: Shared State API + +function getSharedObject(key: string): any { + if (!sharedObjectCache.has(key)) { + sharedObjectCache.set(key, ExpoBrownfieldStateModule.getSharedState(key)); + } + return sharedObjectCache.get(key); +} + +/** + * Gets the value of shared state for a given key. + * + * @param key The key to get the value for. + */ +export function getSharedStateValue(key: string): T | undefined { + const state = getSharedObject(key); + const value = state?.get(); + return value === null ? undefined : (value as T); +} + +/** + * Sets the value of shared state for a given key. + * + * @param key The key to set the value for. + * @param value The value to be set. + */ +export function setSharedStateValue(key: string, value: T): void { + const state = getSharedObject(key); + state.set(value); +} + +/** + * Deletes the shared state for a given key. + * + * @param key The key to delete the shared state for. + */ +export function deleteSharedState(key: string): void { + ExpoBrownfieldStateModule.deleteSharedState(key); + sharedObjectCache.delete(key); +} + +/** + * Adds a listener for changes to the shared state for a given key. + * + * @param key The key to add the listener for. + * @param callback The callback to be called when the shared state changes. + * @returns A subscription object that can be used to remove the listener. + */ +export function addSharedStateListener( + key: string, + callback: (value: T | undefined) => void +): EventSubscription { + const state = getSharedObject(key); + + const subscription = state.addListener('change', (event: T | undefined) => { + callback(event); + }); + + return { + remove: () => subscription.remove(), + }; +} + +/** + * Hook to observe and set the value of shared state for a given key. + * Provides a synchronous API similar to `useState`. + * + * @param key The key to get the value for. + * @param initialValue The initial value to be used if the shared state is not set. + * @returns A tuple containing the value and a function to set the value. + */ +export function useSharedState( + key: string, + initialValue?: T +): [T | undefined, (value: T | ((prev: T | undefined) => T)) => void] { + const state = getSharedObject(key); + + const [value, setValue] = useState(() => { + const currentValue = state.get(); + if (currentValue === null || currentValue === undefined) { + if (initialValue !== undefined) { + state.set(initialValue); + return initialValue; + } + + return undefined; + } + + return currentValue as T; + }); + + useEffect(() => { + const subscription = state.addListener( + 'change', + (event: Record | undefined) => { + setValue(event?.['value']); + } + ); + + return () => subscription.remove(); + }, [state]); + + const setSharedValue = (newValue: T | ((prev: T | undefined) => T)) => { + const valueToSet = + typeof newValue === 'function' ? (newValue as (prev: T | undefined) => T)(value) : newValue; + state.set(valueToSet); + }; + + return [value, setSharedValue]; +} + +// END SECTION: Shared State API diff --git a/packages/expo-brownfield/src/ExpoBrownfieldStateModule.types.ts b/packages/expo-brownfield/src/ExpoBrownfieldStateModule.types.ts new file mode 100644 index 00000000000000..198009eccb8603 --- /dev/null +++ b/packages/expo-brownfield/src/ExpoBrownfieldStateModule.types.ts @@ -0,0 +1,6 @@ +import type { NativeModule } from 'expo'; + +export declare class ExpoBrownfieldStateModuleSpec extends NativeModule { + getSharedState(key: string): any; + deleteSharedState(key: string): void; +} diff --git a/packages/expo-brownfield/src/index.ts b/packages/expo-brownfield/src/index.ts index 6640dbbc2b8060..6401d508c88b95 100644 --- a/packages/expo-brownfield/src/index.ts +++ b/packages/expo-brownfield/src/index.ts @@ -1,83 +1,2 @@ -import type { EventSubscription } from 'expo-modules-core'; - -import ExpoBrownfieldModule from './ExpoBrownfieldModule'; -import type { Listener, MessageEvent } from './types'; - -export { EventSubscription }; -export type { MessageEvent }; - -/** - * Navigates back to the native part of the app, dismissing the React Native view. - * - * @param animated Whether to animate the transition (iOS only). Defaults to `false`. - * @default false - */ -export function popToNative(animated: boolean = false): void { - ExpoBrownfieldModule.popToNative(animated); -} - -/** - * Enables or disables the native back button behavior. When enabled, pressing the - * back button will navigate back to the native part of the app instead of - * performing the default React Navigation back action. - * - * @param enabled Whether to enable native back button handling. - */ -export function setNativeBackEnabled(enabled: boolean): void { - ExpoBrownfieldModule.setNativeBackEnabled(enabled); -} - -/** - * Adds a listener for messages sent from the native side of the app. - * - * @param listener A callback function that receives message events from native. - * @returns A subscription object that can be used to remove the listener. - * - * @example - * ```ts - * const subscription = addMessageListener((event) => { - * console.log('Received message from native:', event); - * }); - * - * // Later, to remove the listener: - * subscription.remove(); - * ``` - */ -export function addMessageListener(listener: Listener): EventSubscription { - return ExpoBrownfieldModule.addListener('onMessage', listener); -} - -/** - * Sends a message to the native side of the app. The message can be received by - * setting up a listener in the native code. - * - * @param message A dictionary containing the message payload to send to native. - */ -export function sendMessage(message: Record): void { - ExpoBrownfieldModule.sendMessage(message); -} - -/** - * Removes a specific message listener. - * - * @param listener The listener function to remove. - */ -export function removeMessageListener(listener: Listener): void { - ExpoBrownfieldModule.removeListener('onMessage', listener); -} - -/** - * Removes all message listeners. - */ -export function removeAllMessageListeners(): void { - ExpoBrownfieldModule.removeAllListeners('onMessage'); -} - -/** - * Gets the number of registered message listeners. - * - * @returns The number of active message listeners. - */ -export function getMessageListenerCount(): number { - return ExpoBrownfieldModule.listenerCount('onMessage'); -} +export * from './ExpoBrownfieldModule'; +export * from './ExpoBrownfieldStateModule';