diff --git a/integration/react-native/MapLibreParticleRNTestHost/App.tsx b/integration/react-native/MapLibreParticleRNTestHost/App.tsx new file mode 100644 index 000000000..0d43bf6ed --- /dev/null +++ b/integration/react-native/MapLibreParticleRNTestHost/App.tsx @@ -0,0 +1,62 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Button, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View, findNodeHandle } from "react-native"; +import { Map } from "@maplibre/maplibre-react-native"; +import { WeatherRuntimeBridge, type RuntimeSnapshot, type TargetFps } from "@maplibre-particle/weather-runtime-bridge"; +import { weatherTestStyle } from "./src/weatherStyle"; + +const initialSnapshot: RuntimeSnapshot = { state: "WAITING_FOR_FIELDS", playbackState: "PAUSE", targetFps: "AUTO", disposed: false }; + +export default function App() { + const [reactTag, setReactTag] = useState(null); + const [snapshot, setSnapshot] = useState(initialSnapshot); + const [result, setResult] = useState("Style is loading"); + const [opacity, setOpacity] = useState("0.62"); + + const attach = useCallback(async (tag: number) => { + try { setSnapshot(await WeatherRuntimeBridge.attach(tag)); setReactTag(tag); setResult("WeatherRuntime attached"); } + catch (error) { setResult(String(error)); } + }, []); + + // MLRN forwards the native MapView event. Its target is the concrete Fabric MLRNMapView tag; + // this is the same tag consumed by MLRN's own NativeMapViewModule. + const onStyleReady = useCallback((event: unknown) => { + const tag = findNodeHandle((event as { target?: unknown }).target as never); + if (tag != null) void attach(tag); + }, [attach]); + + useEffect(() => () => { + if (reactTag != null) void WeatherRuntimeBridge.detach(reactTag).catch(() => undefined); + }, [reactTag]); + + const command = useCallback(async (name: string, operation: (tag: number) => Promise) => { + if (reactTag == null) return setResult("MAP_NOT_READY: wait for style ready"); + try { const value = await operation(reactTag); setResult(`${name}: ${String(value)}`); setSnapshot(await WeatherRuntimeBridge.getState(reactTag)); } + catch (error) { setResult(`${name}: ${String(error)}`); } + }, [reactTag]); + + const buttons = useMemo(() => [ + ["getState", (tag: number) => WeatherRuntimeBridge.getState(tag)], + ["enable", (tag: number) => WeatherRuntimeBridge.setWeatherEnabled(tag, true)], + ["weather visible", (tag: number) => WeatherRuntimeBridge.setWeatherColorVisible(tag, true)], + ["particle visible", (tag: number) => WeatherRuntimeBridge.setParticleVisible(tag, true)], + ["FPS 30", (tag: number) => WeatherRuntimeBridge.setTargetFps(tag, "FPS_30" as TargetFps)], + ["PLAY", (tag: number) => WeatherRuntimeBridge.setPlaybackState(tag, "PLAY")], + ["PAUSE", (tag: number) => WeatherRuntimeBridge.setPlaybackState(tag, "PAUSE")] + ] as const, []); + + return + + + reactTag: {reactTag ?? "not attached"} + runtime: {snapshot.state} / {snapshot.playbackState} / {snapshot.targetFps} + {result} + {buttons.map(([label, operation]) =>