TriloopTem_App/app/(tabs)/waveform.tsx
2026-06-19 22:08:10 +08:00

129 lines
4.9 KiB
TypeScript

import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, useWindowDimensions, ScrollView } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { DeviceStatusBar } from '../../src/components/DeviceStatusBar';
import { WaveformChart } from '../../src/components/WaveformChart';
import { ChannelSelector } from '../../src/components/ChannelSelector';
import { useWaveform } from '../../src/hooks/useWaveform';
import { useDataStore } from '../../src/stores/dataStore';
import { useDeviceStore } from '../../src/stores/deviceStore';
import { formatUV, formatUtc, formatCoord } from '../../src/utils/format';
export default function WaveformScreen() {
const insets = useSafeAreaInsets();
const { width, height: windowHeight } = useWindowDimensions();
const channelNum = useDeviceStore((s) => s.config.channelNum);
const [visible, setVisible] = useState(() => Array(6).fill(true));
const [logScale, setLogScale] = useState(true);
const frame = useDataStore((s) => s.currentFrame);
const data = useWaveform(visible);
const toggleChannel = (idx: number) =>
setVisible((prev) => prev.map((v, i) => (i === idx ? !v : v)));
const CHART_HEIGHT = Math.round(Math.min(300, windowHeight * 0.38));
return (
<View style={[S.container, { paddingTop: insets.top }]}>
<DeviceStatusBar />
<View style={S.toolbar}>
<ChannelSelector maxChannels={channelNum} visible={visible} onToggle={toggleChannel} />
<TouchableOpacity
style={[S.scaleBtn, !logScale && S.scaleBtnActive]}
onPress={() => setLogScale((v) => !v)}
activeOpacity={0.7}
>
<Text style={[S.scaleBtnText, !logScale && S.scaleBtnTextActive]}>
{logScale ? 'LOG' : 'LIN'}
</Text>
</TouchableOpacity>
</View>
{/* Waveform chart */}
<View style={S.chartWrap}>
{data ? (
<WaveformChart
data={data}
visibleChannels={visible}
width={width}
height={CHART_HEIGHT}
logScale={logScale}
/>
) : (
<View style={[S.placeholder, { height: CHART_HEIGHT }]}>
<Text style={S.placeholderText}>...</Text>
</View>
)}
</View>
{/* Per-channel peak values + metadata */}
{frame && (
<ScrollView style={S.peakScroll}>
<View style={S.peakRow}>
{frame.adcUV.map((ch, idx) => {
if (!visible[idx] || idx >= channelNum) return null;
const absMax = ch.reduce((mx, v) => Math.max(mx, Math.abs(v)), 0);
return (
<View key={idx} style={S.peakCell}>
<Text style={S.peakLabel}>CH{idx + 1}</Text>
<Text style={S.peakValue}>{formatUV(absMax)}</Text>
</View>
);
})}
</View>
{frame.meta && (
<View style={S.metaBlock}>
<Text style={S.metaText}>
#{frame.frameId} UTC: {formatUtc(frame.meta.utc)}
</Text>
<Text style={S.metaText}>
{formatCoord(frame.meta.latitude, false)} {formatCoord(frame.meta.longitude, true)} {frame.meta.altitude.toFixed(1)} m
</Text>
<Text style={S.metaText}>
Roll {frame.meta.roll.toFixed(1)}° Pitch {frame.meta.pitch.toFixed(1)}° Yaw {frame.meta.yaw.toFixed(1)}°
</Text>
</View>
)}
</ScrollView>
)}
</View>
);
}
const S = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0d0d0d' },
toolbar: { flexDirection: 'row', alignItems: 'center', paddingRight: 10 },
chartWrap: { borderBottomWidth: 1, borderBottomColor: '#222' },
placeholder: { backgroundColor: '#1a1a1a', justifyContent: 'center', alignItems: 'center' },
placeholderText: { color: '#444', fontSize: 14 },
scaleBtn: {
marginLeft: 8,
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 6,
borderWidth: 1,
borderColor: '#333',
backgroundColor: '#1a1a1a',
},
scaleBtnActive: { borderColor: '#4a9eff55', backgroundColor: '#0d2040' },
scaleBtnText: { color: '#555', fontSize: 11, fontWeight: '700', letterSpacing: 0.5 },
scaleBtnTextActive:{ color: '#4a9eff' },
peakScroll: { flex: 1 },
peakRow: { flexDirection: 'row', flexWrap: 'wrap', padding: 12, gap: 10 },
peakCell: {
backgroundColor: '#1a1a1a',
borderRadius: 8, padding: 10,
minWidth: 90, alignItems: 'center',
borderWidth: 1, borderColor: '#2a2a2a',
},
peakLabel: { color: '#888', fontSize: 11 },
peakValue: { color: '#eee', fontSize: 13, fontWeight: '600', marginTop: 2, fontFamily: 'monospace' },
metaBlock: { margin: 12, backgroundColor: '#111', borderRadius: 8, padding: 10, gap: 3 },
metaText: { color: '#555', fontSize: 10, fontFamily: 'monospace' },
});