Files
MapLibreParticleLab/integration/react-native/MapLibreParticleRNTestHost/App.tsx
2026-09-07 16:09:50 +08:00

63 lines
3.6 KiB
TypeScript

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<number | null>(null);
const [snapshot, setSnapshot] = useState<RuntimeSnapshot>(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<unknown>) => {
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 <SafeAreaView style={styles.root}>
<View style={styles.map}><Map mapStyle={weatherTestStyle} onDidFinishLoadingStyle={onStyleReady} /></View>
<ScrollView style={styles.controls} contentContainerStyle={styles.controlContent}>
<Text>reactTag: {reactTag ?? "not attached"}</Text>
<Text>runtime: {snapshot.state} / {snapshot.playbackState} / {snapshot.targetFps}</Text>
<Text selectable>{result}</Text>
{buttons.map(([label, operation]) => <Button key={label} title={label} onPress={() => void command(label, operation)} />)}
<TextInput value={opacity} onChangeText={setOpacity} keyboardType="decimal-pad" style={styles.input} />
<Button title="weather opacity" onPress={() => void command("weather opacity", (tag) => WeatherRuntimeBridge.setWeatherColorOpacity(tag, Number(opacity)))} />
</ScrollView>
</SafeAreaView>;
}
const styles = StyleSheet.create({
root: { flex: 1 }, map: { flex: 1 }, controls: { maxHeight: 280 }, controlContent: { gap: 8, padding: 12 }, input: { borderWidth: 1, padding: 6 }
});