import React, { useState, useEffect } from 'react'; import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, Switch, Platform } from 'react-native'; import { useDeviceStore } from '../stores/deviceStore'; import { SEND_FREQ_TABLE, SAMPLE_FREQ_TABLE, AMP_RATIO_TABLE, SOURCE_MODE_TABLE } from '../protocol/constants'; interface PickerRowProps { label: string; options: readonly { code: number; label: string }[]; value: number; onChange: (code: number) => void; } function PickerRow({ label, options, value, onChange }: PickerRowProps) { return ( {label} {options.map((o) => { const active = o.code === value; return ( onChange(o.code)} activeOpacity={0.7} > {o.label} ); })} ); } interface NumberRowProps { label: string; value: number; min: number; max: number; onCommit: (v: number) => void; suffix?: string; } // Keeps a local draft string while typing; commits a clamped integer on blur. function NumberRow({ label, value, min, max, onCommit, suffix }: NumberRowProps) { const [draft, setDraft] = useState(String(value)); // Sync when the store value changes externally (e.g. presets). useEffect(() => { setDraft(String(value)); }, [value]); const commit = () => { const n = parseInt(draft, 10); const clamped = isNaN(n) ? min : Math.max(min, Math.min(max, n)); setDraft(String(clamped)); if (clamped !== value) onCommit(clamped); }; const invalid = (() => { const n = parseInt(draft, 10); return isNaN(n) || n < min || n > max; })(); return ( {label} {suffix && {suffix}} {invalid && ( {min}–{max} )} ); } function SectionHeader({ title }: { title: string }) { return ( {title} ); } interface FormProps { onAnyChange?: () => void } export function ParamForm({ onAnyChange }: FormProps = {}) { const { config, updateConfig } = useDeviceStore(); const patch = (partial: Partial) => { updateConfig(partial); onAnyChange?.(); }; return ( {/* Channel count */} 通道数 {[1, 2, 3, 4, 5, 6].map((n) => { const active = config.channelNum === n; return ( patch({ channelNum: n })} activeOpacity={0.7} > {n} ); })} patch({ sendFreq: v })} /> patch({ sampleFreq: v })} /> { patch({ sampleDepth: v }); }} /> { patch({ accNum: v }); }} /> patch({ ampRatio: v })} /> patch({ sourceMode: v })} /> {/* Reverse accumulation */} 反向叠加 CH1-3 patch({ negAcc123: v })} thumbColor={config.negAcc123 ? '#4a9eff' : '#444'} trackColor={{ false: '#1e1e2e', true: '#1a3a66' }} /> CH4-6 patch({ negAcc456: v })} thumbColor={config.negAcc456 ? '#4a9eff' : '#444'} trackColor={{ false: '#1e1e2e', true: '#1a3a66' }} /> { patch({ compDisableDelay: v }); }} suffix="×50μs" /> {/* File prefix */} 文件前缀 patch({ filePrefix: v.slice(0, 15) })} maxLength={15} autoCapitalize="none" placeholderTextColor="#3a3a5a" /> ); } const S = StyleSheet.create({ container: { backgroundColor: '#090912' }, sectionHeader: { paddingHorizontal: 16, paddingTop: 14, paddingBottom: 6, }, sectionTitle: { color: '#3a3a5a', fontSize: 9, fontWeight: '700', letterSpacing: 2, textTransform: 'uppercase', }, row: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 10, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#141422', gap: 12, backgroundColor: '#0e0e1c', marginHorizontal: 0, }, rowLabel: { color: '#4a4a6a', fontSize: 12, width: 68, flexShrink: 0, letterSpacing: 0.3 }, // Picker chips optScroll: { flex: 1 }, optChip: { borderWidth: 1, borderColor: '#1e1e30', borderRadius: 6, paddingHorizontal: 9, paddingVertical: 4, marginRight: 5 }, optChipActive: { borderColor: '#4a9eff55', backgroundColor: '#0d2040' }, optText: { color: '#3a3a5a', fontSize: 11 }, optTextActive: { color: '#4a9eff', fontWeight: '700' }, // Number input inputWrap: { flexDirection: 'row', alignItems: 'center', flex: 1 }, input: { flex: 1, color: '#c0c0d8', fontSize: 13, fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace', paddingVertical: 4, borderBottomWidth: 1, borderBottomColor: '#1e1e30', }, inputInvalid: { borderBottomColor: '#ff5c6e' }, suffix: { color: '#3a3a5a', fontSize: 10, marginLeft: 8 }, hint: { color: '#ff5c6e', fontSize: 9, marginLeft: 4 }, // Channel chips channelRow: { flexDirection: 'row', gap: 6, flex: 1 }, chChip: { width: 30, height: 30, borderRadius: 15, borderWidth: 1, borderColor: '#1e1e30', alignItems: 'center', justifyContent: 'center', backgroundColor: '#0c0c18', }, chChipActive: { borderColor: '#4a9eff66', backgroundColor: '#0d1e3a' }, chText: { color: '#3a3a5a', fontSize: 12, fontWeight: '700' }, chTextActive: { color: '#4a9eff' }, // Switch row switchRow: { flexDirection: 'row', alignItems: 'center', gap: 8, flex: 1 }, switchLabel: { color: '#4a4a6a', fontSize: 12 }, });