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 },
});