powerfun1/src/DfuScreen.tsx
2025-12-19 00:05:42 +08:00

174 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from "react";
import { View, Text, StyleSheet, Alert, BackHandler } from "react-native";
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import { RootStackParamList } from "../App";
import RNFS from "react-native-fs";
import { startDfu, DfuProgressEvent, DfuStateEvent } from "@systemic-games/react-native-nordic-nrf5-dfu";
type Props = NativeStackScreenProps<RootStackParamList, "Dfu">;
interface DeviceInfo {
hardware: number;
latestFirmware: string;
download: string;
}
export default function DfuScreen({ route, navigation }: Props) {
const { deviceId, name, firmware: deviceFirmware } = route.params;
const [progress, setProgress] = useState(0);
const [state, setState] = useState("准备中...");
const [error, setError] = useState<string>();
const [latestVersion, setLatestVersion] = useState<string>("读取中...");
const [isDfuRunning, setIsDfuRunning] = useState(false);
const mapDfuStateToChinese = (state: string): string => {
switch (state) {
case "connecting": return "连接中…";
case "starting": return "初始化中…";
case "enablingDfuMode": return "启用 DFU 模式…";
case "uploading": return "上传固件中…";
case "validating": return "校验固件…";
case "disconnecting": return "断开连接…";
case "completed": return "升级完成";
case "aborted": return "已取消";
case "failed":
case "dfu_failed": return "升级失败";
case "initializing": return "启动中…";
case "errored": return "升级出错!";
default: return state;
}
};
// ✅ 拦截所有导航返回iOS + Android
useEffect(() => {
const unsubscribe = navigation.addListener("beforeRemove", (e) => {
if (!isDfuRunning) return;
// 阻止返回
e.preventDefault();
Alert.alert("请稍候", "正在升级,请勿返回或关闭应用!");
});
return unsubscribe;
}, [navigation, isDfuRunning]);
useEffect(() => {
const runDfu = async () => {
try {
setIsDfuRunning(true);
const manifestUrl = "https://powerfun.oss-cn-shanghai.aliyuncs.com/yecongdfu/latest.json";
const manifestPath = RNFS.CachesDirectoryPath + "/latest.json";
await RNFS.downloadFile({ fromUrl: manifestUrl, toFile: manifestPath }).promise;
const manifestContent = await RNFS.readFile(manifestPath);
const manifest = JSON.parse(manifestContent) as { devices: DeviceInfo[] };
const [deviceHWStr, deviceFWStr] = deviceFirmware.split(".");
const deviceHW = parseInt(deviceHWStr);
const deviceFW = parseInt(deviceFWStr);
const deviceInfo = manifest.devices.find(d => d.hardware === deviceHW);
if (!deviceInfo) {
setIsDfuRunning(false);
Alert.alert("无法升级", `未找到硬件版本 ${deviceHW} 的固件`, [
{ text: "确认", onPress: () => navigation.goBack() },
]);
return;
}
setLatestVersion(deviceInfo.latestFirmware);
const [, latestFWStr] = deviceInfo.latestFirmware.split(".");
const latestFW = parseInt(latestFWStr);
if (latestFW <= deviceFW) {
setIsDfuRunning(false);
Alert.alert("无需升级", "已是最新固件,无需升级", [
{ text: "确认", onPress: () => navigation.goBack() },
]);
return;
}
const localPath = RNFS.CachesDirectoryPath + "/firmware.zip";
await RNFS.downloadFile({ fromUrl: deviceInfo.download, toFile: localPath }).promise;
await startDfu(deviceId, "file://" + localPath, {
dfuStateListener: (ev: DfuStateEvent) => setState(ev.state),
dfuProgressListener: (ev: DfuProgressEvent) => setProgress(ev.percent),
});
setIsDfuRunning(false);
Alert.alert("升级成功", "升级成功,请重连设备", [
{ text: "确认", onPress: () => navigation.navigate("Home") },
]);
} catch (err: any) {
setIsDfuRunning(false);
setError(err.message || "DFU失败");
Alert.alert("升级失败", err.message || "DFU失败", [
{ text: "确认", onPress: () => navigation.goBack() },
]);
}
};
runDfu();
}, [deviceId, deviceFirmware, navigation]);
return (
<View style={{ flex: 1, padding: 20 }}>
<View style={styles.row}>
<Text style={{ fontSize: 16 }}>: {name}</Text>
</View>
<View style={styles.row}>
<Text style={{ fontSize: 16 }}>: {latestVersion}</Text>
</View>
<View style={styles.row}>
<Text style={{ fontSize: 16 }}>: {deviceFirmware}</Text>
</View>
<View style={styles.row}>
<Text style={{ fontSize: 16 }}>: {mapDfuStateToChinese(state)}</Text>
</View>
{/* 横向进度条 */}
<View style={styles.progressContainer}>
<View style={[styles.progressBar, { width: `${progress}%` }]} />
<Text style={styles.progressText}>{progress}%</Text>
</View>
{error && <Text style={{ color: "red", marginTop: 20 }}>{error}</Text>}
</View>
);
}
const styles = StyleSheet.create({
row: {
borderBottomWidth: 1,
borderBottomColor: "red",
paddingBottom: 4,
marginBottom: 8,
},
progressContainer: {
height: 30,
backgroundColor: "#eee",
borderRadius: 15,
overflow: "hidden",
marginTop: 40,
justifyContent: "center",
},
progressBar: {
height: "100%",
backgroundColor: "#E7141E",
},
progressText: {
position: "absolute",
alignSelf: "center",
fontWeight: "bold",
color: "#000",
},
});