208 lines
6.8 KiB
TypeScript
208 lines
6.8 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
ScrollView,
|
|
Linking,
|
|
ActivityIndicator,
|
|
} from 'react-native';
|
|
import { router } from 'expo-router';
|
|
import { useConnectionStore } from '../src/stores/connectionStore';
|
|
import { useDevice } from '../src/hooks/useDevice';
|
|
|
|
const LOG_MAX = 60;
|
|
|
|
export default function ConnectScreen() {
|
|
const { host, port, status, setHost, setPort } = useConnectionStore();
|
|
const { connect, disconnect } = useDevice();
|
|
const [logs, setLogs] = useState<string[]>(['准备连接...']);
|
|
const [portStr, setPortStr] = useState(String(port));
|
|
|
|
const addLog = (msg: string) => {
|
|
setLogs((prev) => [`[${new Date().toLocaleTimeString()}] ${msg}`, ...prev].slice(0, LOG_MAX));
|
|
};
|
|
|
|
const handleConnect = () => {
|
|
const p = parseInt(portStr, 10);
|
|
if (!host || isNaN(p)) {
|
|
addLog('请检查 IP 和端口');
|
|
return;
|
|
}
|
|
setPort(p);
|
|
addLog(`正在连接 ${host}:${p}...`);
|
|
|
|
// Watch for status changes via the hook (side effect in useDevice)
|
|
useConnectionStore.subscribe((s) => {
|
|
if (s.status === 'connected') {
|
|
addLog('连接成功!');
|
|
router.replace('/(tabs)/control');
|
|
} else if (s.status === 'error') {
|
|
addLog(`错误: ${s.lastError}`);
|
|
} else if (s.status === 'reconnecting') {
|
|
addLog('断线,正在重连...');
|
|
}
|
|
});
|
|
|
|
connect();
|
|
};
|
|
|
|
const handleDisconnect = () => {
|
|
disconnect();
|
|
addLog('已断开连接');
|
|
};
|
|
|
|
const isConnecting = status === 'connecting' || status === 'reconnecting';
|
|
const isConnected = status === 'connected';
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Text style={styles.title}>TEM Receiver</Text>
|
|
<Text style={styles.subtitle}>瞬变电磁接收机上位机</Text>
|
|
|
|
{/* Connection steps */}
|
|
<View style={styles.card}>
|
|
<Text style={styles.cardTitle}>连接步骤</Text>
|
|
<Text style={styles.step}>① 在手机 WiFi 设置中连接设备热点</Text>
|
|
<TouchableOpacity style={styles.wifiBtn} onPress={() => Linking.openSettings()}>
|
|
<Text style={styles.wifiBtnText}>打开 WiFi 设置 →</Text>
|
|
</TouchableOpacity>
|
|
<Text style={styles.step}>② 确认连接参数后点击连接</Text>
|
|
</View>
|
|
|
|
{/* IP / Port input */}
|
|
<View style={styles.card}>
|
|
<View style={styles.inputRow}>
|
|
<Text style={styles.inputLabel}>设备 IP</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={host}
|
|
onChangeText={setHost}
|
|
keyboardType="numeric"
|
|
placeholder="192.168.4.1"
|
|
placeholderTextColor="#555"
|
|
autoCapitalize="none"
|
|
/>
|
|
</View>
|
|
<View style={styles.inputRow}>
|
|
<Text style={styles.inputLabel}>端口</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={portStr}
|
|
onChangeText={setPortStr}
|
|
keyboardType="numeric"
|
|
placeholder="4321"
|
|
placeholderTextColor="#555"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Connect / Disconnect button */}
|
|
{!isConnected ? (
|
|
<TouchableOpacity
|
|
style={[styles.connectBtn, isConnecting && styles.connectBtnBusy]}
|
|
onPress={handleConnect}
|
|
disabled={isConnecting}
|
|
>
|
|
{isConnecting ? (
|
|
<ActivityIndicator color="#fff" />
|
|
) : (
|
|
<Text style={styles.connectBtnText}>连 接 设 备</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
) : (
|
|
<View style={styles.connectedRow}>
|
|
<View style={styles.connectedBadge}>
|
|
<View style={styles.greenDot} />
|
|
<Text style={styles.connectedText}>已连接</Text>
|
|
</View>
|
|
<TouchableOpacity style={styles.goBtn} onPress={() => router.replace('/(tabs)/control')}>
|
|
<Text style={styles.goBtnText}>进入控制台 →</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity style={styles.disconnectBtn} onPress={handleDisconnect}>
|
|
<Text style={styles.disconnectBtnText}>断开</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
)}
|
|
|
|
{/* Connection log */}
|
|
<View style={styles.logCard}>
|
|
<Text style={styles.logTitle}>连接日志</Text>
|
|
<ScrollView style={styles.logScroll} nestedScrollEnabled>
|
|
{logs.map((l, i) => (
|
|
<Text key={i} style={styles.logLine}>{l}</Text>
|
|
))}
|
|
</ScrollView>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: '#0d0d0d', padding: 16 },
|
|
title: { color: '#4a9eff', fontSize: 28, fontWeight: '700', textAlign: 'center', marginTop: 40 },
|
|
subtitle: { color: '#666', fontSize: 13, textAlign: 'center', marginBottom: 24 },
|
|
card: {
|
|
backgroundColor: '#1a1a1a',
|
|
borderRadius: 10,
|
|
padding: 14,
|
|
marginBottom: 12,
|
|
borderWidth: 1,
|
|
borderColor: '#2a2a2a',
|
|
},
|
|
cardTitle: { color: '#4a9eff', fontSize: 13, fontWeight: '600', marginBottom: 8 },
|
|
step: { color: '#bbb', fontSize: 13, marginBottom: 6 },
|
|
wifiBtn: {
|
|
backgroundColor: '#0d2b55',
|
|
borderRadius: 6,
|
|
paddingVertical: 6,
|
|
paddingHorizontal: 12,
|
|
alignSelf: 'flex-start',
|
|
marginBottom: 10,
|
|
},
|
|
wifiBtnText: { color: '#4a9eff', fontSize: 12 },
|
|
inputRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 8 },
|
|
inputLabel: { color: '#888', fontSize: 13, width: 50 },
|
|
input: {
|
|
flex: 1,
|
|
backgroundColor: '#111',
|
|
color: '#eee',
|
|
borderRadius: 6,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 6,
|
|
fontSize: 14,
|
|
borderWidth: 1,
|
|
borderColor: '#333',
|
|
},
|
|
connectBtn: {
|
|
backgroundColor: '#4a9eff',
|
|
borderRadius: 10,
|
|
paddingVertical: 14,
|
|
alignItems: 'center',
|
|
marginBottom: 12,
|
|
},
|
|
connectBtnBusy: { backgroundColor: '#2a5a99' },
|
|
connectBtnText: { color: '#fff', fontSize: 16, fontWeight: '700', letterSpacing: 2 },
|
|
connectedRow: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 12 },
|
|
connectedBadge: { flexDirection: 'row', alignItems: 'center', gap: 6 },
|
|
greenDot: { width: 10, height: 10, borderRadius: 5, backgroundColor: '#44cc44' },
|
|
connectedText: { color: '#44cc44', fontWeight: '600' },
|
|
goBtn: { flex: 1, backgroundColor: '#1a4a22', borderRadius: 8, padding: 10, alignItems: 'center' },
|
|
goBtnText: { color: '#44cc44', fontWeight: '600' },
|
|
disconnectBtn: { backgroundColor: '#3a1010', borderRadius: 8, padding: 10 },
|
|
disconnectBtnText: { color: '#ff6666' },
|
|
logCard: {
|
|
flex: 1,
|
|
backgroundColor: '#111',
|
|
borderRadius: 10,
|
|
padding: 10,
|
|
borderWidth: 1,
|
|
borderColor: '#222',
|
|
},
|
|
logTitle: { color: '#666', fontSize: 11, marginBottom: 4 },
|
|
logScroll: { flex: 1 },
|
|
logLine: { color: '#555', fontSize: 10, fontFamily: 'monospace', marginBottom: 2 },
|
|
});
|