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 { writeCanonicalWindFixture } from "./src/canonicalWindFixture"; 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 [binaryTransport, setBinaryTransport] = useState("PENDING: submit fixture to validate FILE_REF / FLOAT32_LE / INTERLEAVED_UV"); const [invalidProbe, setInvalidProbe] = useState("PENDING"); const [t0Submit, setT0Submit] = useState("PENDING"); const [t1Submit, setT1Submit] = useState("PENDING"); const [fieldWindow, setFieldWindow] = useState("TIMELINE_NOT_READY"); 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 submitBinaryFixture = useCallback(async () => { if (reactTag == null) return setResult("MAP_NOT_READY: wait for style ready"); try { setBinaryTransport("PENDING: clearing fields and validating FILE_REF / FLOAT32_LE / INTERLEAVED_UV"); setInvalidProbe("PENDING"); setT0Submit("PENDING"); setT1Submit("PENDING"); setFieldWindow("PENDING: awaiting Core initial window"); const clear = await WeatherRuntimeBridge.clearWindFields(reactTag); if (clear !== "ACCEPTED") throw new Error(`clearWindFields: ${clear}`); const [t0, t1, invalid] = writeCanonicalWindFixture(); const rejected = await WeatherRuntimeBridge.submitWindField(reactTag, invalid.metadataJson, invalid.payloadFileUri); setInvalidProbe(rejected); if (rejected !== "REJECTED_ADAPTER") throw new Error(`invalid probe expected REJECTED_ADAPTER, got ${rejected}`); const t0Result = await WeatherRuntimeBridge.submitWindField(reactTag, t0.metadataJson, t0.payloadFileUri); setT0Submit(t0Result); if (t0Result !== "ACCEPTED_PENDING_INITIAL_WINDOW") throw new Error(`T0 expected ACCEPTED_PENDING_INITIAL_WINDOW, got ${t0Result}`); const t1Result = await WeatherRuntimeBridge.submitWindField(reactTag, t1.metadataJson, t1.payloadFileUri); setT1Submit(t1Result); if (t1Result !== "INITIAL_WINDOW_READY") throw new Error(`T1 expected INITIAL_WINDOW_READY, got ${t1Result}`); const native = await WeatherRuntimeBridge.getState(reactTag); setSnapshot(native); if (native.state !== "READY") throw new Error(`Native WeatherRuntime expected READY, got ${native.state}`); setFieldWindow(`Core evidence: INITIAL_WINDOW_READY + runtime READY; producer identity: T0=${t0.generation}@${t0.validTime}, T1=${t1.generation}@${t1.validTime}; MIX: Core-owned / not exposed`); setBinaryTransport("READY: FILE_REF / FLOAT32_LE / INTERLEAVED_UV"); setResult(`Binary field submit: invalid=${rejected}, T0=${t0Result}, T1=${t1Result}`); } catch (error) { setBinaryTransport(`ERROR: ${String(error)}`); setResult(`Binary field submit: ${String(error)}`); setFieldWindow("TIMELINE_NOT_READY"); } }, [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} Binary Transport: {binaryTransport} Invalid Probe: {invalidProbe} T0 Submit Result: {t0Submit} T1 Submit Result: {t1Submit} Runtime Field Window: {fieldWindow} {result}