import React, { useCallback, useMemo, useState } from 'react'; import { StatusBar, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { callback } from 'react-native-nitro-modules'; import type { CameraState, CesiumMetrics } from 'react-native-cesium'; import { CesiumView } from 'react-native-cesium'; import { SafeAreaProvider, useSafeAreaInsets, } from 'react-native-safe-area-context'; import { CESIUM_ION_ACCESS_TOKEN, ION_ACCESS_TOKEN } from '@env'; import { Joystick } from './components/Joystick'; import { LayerPicker } from './components/LayerPicker'; import { CreditsDialog } from './components/CreditsDialog'; import { MapGestureHandler } from './components/MapGestureHandler'; import { useCameraController } from './hooks/useCameraController'; const ionAccessToken = (CESIUM_ION_ACCESS_TOKEN ?? ION_ACCESS_TOKEN ?? '').trim(); const hasIonToken = ionAccessToken.length > 0; const INITIAL_CAMERA: CameraState = { latitude: 46.02, longitude: 7.6, altitude: 5800, heading: 220, pitch: -20, roll: 0, verticalFovDeg: 60, }; function AppContent() { const insets = useSafeAreaInsets(); const [imageryAssetId, setImageryAssetId] = useState(1); const [credits, setCredits] = useState(''); const [creditsExpanded, setCreditsExpanded] = useState(false); const [tilesLoading, setTilesLoading] = useState(0); const [tilesRendered, setTilesRendered] = useState(0); const [fps, setFps] = useState(0); const { camera, hudCamera, setCesiumView, handleJoystickRates } = useCameraController(INITIAL_CAMERA); const hybridRef = useMemo(() => callback(setCesiumView), [setCesiumView]); const handleMetrics = useCallback((m: CesiumMetrics) => { setCredits((prev) => (prev === m.creditsPlainText ? prev : m.creditsPlainText)); setTilesLoading(Math.round(m.tilesLoading)); setTilesRendered(Math.round(m.tilesRendered)); setFps(Math.round(m.fps)); }, []); const metricsCallback = useMemo( () => callback(handleMetrics), [handleMetrics], ); const handleCloseCredits = useCallback(() => setCreditsExpanded(false), []); const handleOpenCredits = useCallback(() => setCreditsExpanded(true), []); return ( {!hasIonToken && ( Cesium Ion token missing Create `example/.env` from `example/.env_example` and set `CESIUM_ION_ACCESS_TOKEN`. )} {hasIonToken && ( Lat {hudCamera.latitude.toFixed(5)}°, Lon{' '} {hudCamera.longitude.toFixed(5)}° Alt {Math.round(hudCamera.altitude).toLocaleString()} m Heading {hudCamera.heading.toFixed(1)}° Tiles {tilesRendered} rendered · {tilesLoading} loading · {fps} fps )} 0 ? 24 : 0) }, ]} > Pitch / Roll {credits.length > 0 && ( ⓘ {credits} )} ); } function App() { return ( ); } const styles = StyleSheet.create({ rootFill: { flex: 1 }, container: { flex: 1, backgroundColor: '#000' }, bottomCenter: { position: 'absolute', alignSelf: 'center', alignItems: 'center', gap: 10, }, joystickLabel: { color: 'rgba(255,255,255,0.55)', fontSize: 10, fontWeight: '500', letterSpacing: 0.5, }, creditsBar: { position: 'absolute', left: 8, right: 8, backgroundColor: 'rgba(0,0,0,0.50)', borderRadius: 4, paddingHorizontal: 8, paddingVertical: 3, }, creditsBarText: { textAlign: 'center', fontSize: 10, color: 'rgba(255,255,255,0.70)', }, cameraHud: { position: 'absolute', left: 12, backgroundColor: 'rgba(0,0,0,0.32)', borderRadius: 8, paddingHorizontal: 10, paddingVertical: 8, gap: 2, }, cameraHudText: { color: '#fff', fontSize: 12, fontVariant: ['tabular-nums'], }, missingTokenBanner: { position: 'absolute', left: 12, right: 12, backgroundColor: 'rgba(120, 20, 20, 0.92)', borderRadius: 10, paddingHorizontal: 12, paddingVertical: 10, gap: 4, }, missingTokenTitle: { color: '#fff', fontSize: 14, fontWeight: '700', }, missingTokenBody: { color: 'rgba(255,255,255,0.9)', fontSize: 12, lineHeight: 18, }, }); export default App;