import React, { useEffect, useState, useCallback, useRef } from "react"; import { View, Text as RNText, TextInput, ScrollView, StyleSheet, ActivityIndicator, useColorScheme, Alert, Pressable, } from "react-native"; import { useFocusEffect } from "@react-navigation/native"; import { NativeStackScreenProps } from "@react-navigation/native-stack"; import { RootStackParamList } from "../App"; import { Central, ScannedPeripheral, ConnectionStatus, } from "@systemic-games/react-native-bluetooth-le"; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; type Props = NativeStackScreenProps; const powerServiceUuid = "fff1"; const powerWriteUuid = "fff2"; const powerNotifyUuid = "fff3"; const fullUUID = (uuid: string) => uuid.length === 4 ? `0000${uuid}-0000-1000-8000-00805f9b34fb` : uuid; const Text = ({ children, style, ...props }: any) => { const isDark = useColorScheme() === "dark"; return ( {children} ); }; export default function InfoScreen({ route, navigation }: Props) { const { peripheral } = route.params; const deviceKey = peripheral.address || peripheral.systemId; const [isConnected, setIsConnected] = useState(false); const [isLoading, setIsLoading] = useState(true); const [serial, setSerial] = useState("读取中..."); const [firmware, setFirmware] = useState("读取中..."); const [hardware, setHardware] = useState("读取中..."); const [battery, setBattery] = useState("读取中..."); const [powerTrim, setPowerTrim] = useState("读取中"); const [inputTrim, setInputTrim] = useState("100"); const [readSuccessToast, setReadSuccessToast] = useState(false); // ✅ 读取成功提示 const [powerTrimLoading, setPowerTrimLoading] = useState(false); // 正在写入功率微调 const [powerTrimSuccessToast, setPowerTrimSuccessToast] = useState(false); // 功率微调写入成功 const notifySubscribedRef = useRef(false); const disconnectingRef = useRef(false); const prevConnectedRef = useRef(isConnected); const isActiveRef = useRef(true); useFocusEffect( React.useCallback(() => { // 页面获得焦点 isActiveRef.current = true; return () => { // 页面失去焦点 isActiveRef.current = false; }; }, []) ); // ========== 监听连接状态变化,断开时提示重新连接 ========== useEffect(() => { if (prevConnectedRef.current && !isConnected && isActiveRef.current) { Alert.alert("提示", "请重新连接设备", [ { text: "确定", onPress: () => navigation.goBack() }, ]); } prevConnectedRef.current = isConnected; }, [isConnected]); // ========== 页面即将返回时,强制断开蓝牙 ========== useEffect(() => { const unsubscribe = navigation.addListener("beforeRemove", async (e) => { if (disconnectingRef.current) return; disconnectingRef.current = true; const next = e.data.action?.type; const action = e.data.action as any; // ✅ 判断是否跳转到 Dfu 页面 if (next === "NAVIGATE" && action?.payload?.name === "Dfu") { console.log("➡️ 跳转到 DFU,不断开蓝牙"); disconnectingRef.current = false; return; } // 🔌 其他页面离开则断开蓝牙 console.log("📴 页面关闭,断开蓝牙..."); try { await Central.disconnectPeripheral(peripheral); console.log("✅ 已断开蓝牙"); } catch (err) { console.warn("❌ 断开失败:", err); } finally { disconnectingRef.current = false; } }); return unsubscribe; }, [navigation, peripheral]); // ========== BLE notify 订阅 ========== useEffect(() => { const connectionHandler = async (ev: { peripheral: ScannedPeripheral; connectionStatus: ConnectionStatus; }) => { const addr = ev.peripheral.address || ev.peripheral.systemId; if (addr !== deviceKey) return; console.log("🔌 connection event:", ev.connectionStatus); setIsConnected( ev.connectionStatus === "connected" || ev.connectionStatus === "ready" ); if (ev.connectionStatus === "ready" && !notifySubscribedRef.current) { console.log("✅ device ready - subscribe notify FFF3"); try { await Central.unsubscribeCharacteristic( peripheral, fullUUID(powerServiceUuid), fullUUID(powerNotifyUuid) ).catch(() => {}); await Central.subscribeCharacteristic( peripheral, fullUUID(powerServiceUuid), fullUUID(powerNotifyUuid), (notifyEv) => { try { const raw = (notifyEv as any).value; if (!raw) return; const arr = raw instanceof Uint8Array ? Array.from(raw) : Array.from(new Uint8Array(raw)); if (arr[0] === 0x02 && arr.length >= 2) { setPowerTrim(arr[1].toString()); setInputTrim(arr[1].toString()); } } catch (err) { console.warn("notify parse error", err); } } ); notifySubscribedRef.current = true; console.log("✅ notify 已订阅"); } catch (err) { console.warn("❌ notify 订阅失败:", err); } } }; Central.addListener("peripheralConnectionStatus", connectionHandler); return () => { Central.removeListener("peripheralConnectionStatus", connectionHandler); notifySubscribedRef.current = false; }; }, [deviceKey, peripheral]); // ========== 首次连接并读取信息 ========== useEffect(() => { (async () => { setIsLoading(true); try { await Central.connectPeripheral(peripheral); await new Promise((resolve) => setTimeout(resolve, 500)); const readStr = async (srv: string, char: string) => { try { const v = await Central.readCharacteristic( peripheral, fullUUID(srv), fullUUID(char) ); return v ? String.fromCharCode(...(v as any)) : "未知"; } catch { return "未知"; } }; setSerial(await readStr("180a", "2a25")); setFirmware(await readStr("180a", "2a28")); setHardware(await readStr("180a", "2a27")); try { const v = await Central.readCharacteristic( peripheral, fullUUID("180f"), fullUUID("2a19") ); if (v && (v as any).length) setBattery(`${(v as any)[0]}%`); } catch { setBattery("未知"); } console.log("✅ info 读取完成"); try { await Central.writeCharacteristic( peripheral, fullUUID(powerServiceUuid), fullUUID(powerWriteUuid), new Uint8Array([0x04]).buffer, { withoutResponse: false } ); console.log("✅ 已发送 0x04,等待 notify 更新功率微调"); // notify 回调里已经订阅了,所以这里不用再重复订阅 // 可以稍等 300ms,确保 notify 回来后 UI 会更新 await new Promise(resolve => setTimeout(() => resolve(), 300)); } catch (err) { console.warn("❌ 首次读取功率微调失败", err); } } catch (e) { console.warn("❌ 读取失败", e); } finally { setIsLoading(false); // ✅ 显示读取成功提示 2 秒 setReadSuccessToast(true); setTimeout(() => setReadSuccessToast(false), 2000); } })(); }, [peripheral]); // ========== 写入功率微调 ========== const updatePowerTrim = async () => { const val = parseInt(inputTrim); if (isNaN(val) || val < 50 || val > 200) { Alert.alert("提示", "请输入有效数字(50~200)"); return; } if (val === Number(powerTrim)) { console.log("⚙️ 相同数值,跳过发送"); return; } if (!isConnected) { Alert.alert("提示", "设备未连接"); return; } try { setPowerTrimLoading(true); console.log("🚀 写入功率微调", val); await Central.writeCharacteristic( peripheral, fullUUID(powerServiceUuid), fullUUID(powerWriteUuid), new Uint8Array([0x02, val]).buffer, { withoutResponse: false } ); await new Promise((resolve) => setTimeout(() => resolve(), 150)); await Central.writeCharacteristic( peripheral, fullUUID(powerServiceUuid), fullUUID(powerWriteUuid), new Uint8Array([0x04]).buffer, { withoutResponse: false } ); setPowerTrimLoading(false); setPowerTrimSuccessToast(true); setTimeout(() => setPowerTrimSuccessToast(false), 2000); } catch (err) { console.warn("❌ 写入失败", err); Alert.alert("写入失败"); } }; // ========== UI ========== return ( 蓝牙名称: {peripheral.name} ID号: {serial} 固件版本: {firmware} 电量: {battery} 连接状态: {isConnected ? "已连接" : "未连接"} 功率微调: {powerTrim}% 更新功率微调 { if (!isConnected) { Alert.alert("提示", "请重新连接设备", [ { text: "确定", onPress: () => { // 返回 ScanScreen navigation.goBack(); }, }, ]); return; } // 蓝牙已连接,正常跳转 DFU navigation.navigate("Dfu", { deviceId: deviceKey, name: peripheral.name, firmware, }); }} style={styles.pressable} disabled={isLoading || powerTrimLoading} // ❌ 禁用点击 > 升级固件 {isLoading && ( 正在读取信息... )} {/* ✅ 读取成功提示,固定在页面底部 */} {readSuccessToast && ( 读取成功! )} {/* 正在写入功率微调 */} {powerTrimLoading && ( 正在写入功率微调... )} {/* 功率微调写入成功 */} {powerTrimSuccessToast && ( 功率微调更新成功! )} ); } const styles = StyleSheet.create({ row: { borderBottomWidth: 1, borderBottomColor: "#E7141E", paddingBottom: 4, // 横线和文字保持一定间距 marginBottom: 8, // 行间距 }, input: { borderWidth: 1, borderColor: "#ccc", padding: 6, marginTop: 5, borderRadius: 4, }, pressable: { padding: 10, backgroundColor: "#E7141E", marginTop: 10, borderRadius: 6, alignItems: "center", }, });