import React, { useState, useEffect, useRef } from 'react'; import { View, Text, TextInput, TouchableOpacity, StyleSheet, Modal, ScrollView, ActivityIndicator, KeyboardAvoidingView, Platform, Linking, } from 'react-native'; import { useConnectionStore } from '../../stores/connectionStore'; import { useDevice } from '../../hooks/useDevice'; import { useTheme } from '../../design/tokens'; import { Radius, Spacing } from '../../design/tokens'; const LOG_MAX = 20; export function ConnectModal() { const theme = useTheme(); const { status, host, port, lastError, modalVisible, setHost, setPort, hideModal } = useConnectionStore(); const { connect, disconnect } = useDevice(); const [portStr, setPortStr] = useState(String(port)); const [logs, setLogs] = useState(['就绪']); const prevStatus = useRef(status); const addLog = (msg: string) => setLogs((p) => [`${new Date().toLocaleTimeString('zh-CN', { hour12: false })} ${msg}`, ...p].slice(0, LOG_MAX)); // Sync portStr when store port changes (e.g. from persisted storage) useEffect(() => { setPortStr(String(port)); }, [port]); useEffect(() => { if (status === prevStatus.current) return; prevStatus.current = status; if (status === 'connected') { addLog('连接成功 ✓'); const t = setTimeout(() => hideModal(), 900); return () => clearTimeout(t); } if (status === 'error') addLog(`错误: ${lastError}`); if (status === 'reconnecting') addLog('断线,重连中…'); if (status === 'disconnected') addLog('已断开'); }, [status, lastError]); const handleConnect = () => { const trimmed = host.trim(); const p = parseInt(portStr, 10); const ipRe = /^(\d{1,3}\.){3}\d{1,3}$/; if (!trimmed || !ipRe.test(trimmed) || trimmed.split('.').some(s => Number(s) > 255)) { addLog('IP 地址格式无效'); return; } if (isNaN(p) || p < 1 || p > 65535) { addLog('端口范围 1-65535'); return; } setPort(p); addLog(`连接 ${trimmed}:${p} …`); connect(); }; const handleDisconnect = () => { disconnect(); addLog('主动断开'); }; const connecting = status === 'connecting' || status === 'reconnecting'; const connected = status === 'connected'; return ( {/* Handle */} {/* Title */} 连接仪器 {/* Guide */} 1 手机连接设备 WiFi 热点 Linking.openSettings()} > 设置 → 2 确认参数后点击连接 {/* Inputs */} IP 地址 端 口 {/* Action */} {!connected ? ( {connecting ? : 连 接 设 备} ) : ( 已连接 {host}:{port} 断 开 )} {/* Log */} ● 连接日志 {logs.map((l, i) => ( {l} ))} ); } const S = StyleSheet.create({ backdrop: { ...StyleSheet.absoluteFill, backgroundColor: 'rgba(0,0,0,0.6)', }, kav: { flex: 1, justifyContent: 'flex-end', }, sheet: { borderTopLeftRadius: Radius.xl, borderTopRightRadius: Radius.xl, padding: Spacing.lg, paddingBottom: Spacing.xl, borderTopWidth: 1, }, handle: { width: 36, height: 4, borderRadius: 2, alignSelf: 'center', marginBottom: Spacing.md, }, title: { fontSize: 15, fontWeight: '700', marginBottom: Spacing.md, }, guideRow: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, marginBottom: 6, }, stepBadge: { width: 20, height: 20, borderRadius: 10, justifyContent: 'center', alignItems: 'center', borderWidth: 1, }, stepNum: { fontSize: 10, fontWeight: '700' }, stepTxt: { fontSize: 12, flex: 1 }, settingsBtn: { borderRadius: Radius.sm, paddingHorizontal: 8, paddingVertical: 3, borderWidth: 1, }, settingsTxt: { fontSize: 11, fontWeight: '600' }, inputCard: { borderRadius: Radius.md, borderWidth: 1, paddingHorizontal: Spacing.md, marginBottom: Spacing.md, }, inputRow: { flexDirection: 'row', alignItems: 'center', gap: Spacing.md, paddingVertical: Spacing.sm }, inputDivider: { height: StyleSheet.hairlineWidth }, inputLabel: { fontSize: 12, width: 52 }, input: { flex: 1, fontSize: 15, fontWeight: '500', paddingVertical: 4, borderBottomWidth: 1, }, connectBtn: { borderRadius: Radius.lg, paddingVertical: 15, alignItems: 'center', marginBottom: Spacing.md, borderWidth: 1, }, connectBtnTxt: { fontSize: 15, fontWeight: '700', letterSpacing: 3 }, connectedRow: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, marginBottom: Spacing.md }, connectedLeft: { flexDirection: 'row', alignItems: 'center', gap: 6, flex: 1 }, greenDot: { width: 8, height: 8, borderRadius: 4 }, connectedTxt: { fontWeight: '700', fontSize: 12, fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace', }, disconnectBtn: { borderRadius: Radius.md, paddingVertical: 8, paddingHorizontal: 14, borderWidth: 1, }, disconnectTxt: { fontSize: 12, fontWeight: '700', letterSpacing: 1 }, logBox: { borderRadius: Radius.md, borderWidth: 1, overflow: 'hidden', maxHeight: 120, }, logHeader: { fontSize: 9, fontWeight: '700', letterSpacing: 1.5, paddingHorizontal: Spacing.md, paddingTop: 8, paddingBottom: 5, borderBottomWidth: 1, }, logScroll: { padding: Spacing.sm }, logLine: { fontSize: 10, fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace', marginBottom: 2, lineHeight: 15, }, });