import { Ionicons } from '@expo/vector-icons' import { root } from '@repo/core' import { FirmwareController } from '@repo/sdk' import { Block, ConfirmModal, ListEmpty, SyncProgressToast, Text, Toast, VideoBox } from '@share/components' import { FlashList } from '@shopify/flash-list' import { router, Stack, useFocusEffect } from 'expo-router' import { observer } from 'mobx-react-lite' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { ActivityIndicator } from 'react-native' import { bleManager } from '@/ble/managers/bleManager' import BannerSection from '@/components/BannerSection' import { bleStore, userStore } from '@/stores' import { screenWidth } from '@/utils' import { buildCdnUrl } from '@/utils/getCDNKey' // ============ 主组件 ============ const Device = observer(() => { const { user } = userStore const { isConnected, sn } = bleStore.state const { galleryList } = bleStore const itemWidth = Math.floor((screenWidth - 12 * 2 - 12 * 2) / 3) // ✅ 本地状态:直接从 Promise 获取数据 const [deviceInfo, setDeviceInfo] = useState(null) const [version, setVersion] = useState('') const [memoryStats, setMemoryStats] = useState({ total: 0n, used: 0n, available: 0n, }) const [loading, setLoading] = useState(false) const [binInfo, setBinInfo] = useState(null) const loadBin = async () => { const firmware = root.get(FirmwareController) const status = process.env.EXPO_PUBLIC_ENV === 'production' ? 'PUBLISHED' : 'DRAFT' firmware.getLatestPublished('duomi', status).then((r) => { console.log('latest duomi firmware:', r) setBinInfo(r) }) } useEffect(() => { console.log('expo env------------', process.env.EXPO_PUBLIC_ENV) }, []) // ✅ 页面聚焦时自动获取设备信息和版本号 useFocusEffect( useCallback(() => { if (isConnected) { queryDeviceData() } }, [isConnected]), ) useEffect(() => { if (!isConnected) { Toast.show({ title: '设备已断开连接' }) router.back() } }, [isConnected]) // ✅ 直接从 Promise 获取数据 const queryDeviceData = useCallback(async () => { try { setLoading(true) loadBin() // 并行获取设备版本号和设备信息 const [versionInfo, deviceInfoData] = await Promise.all([ bleManager.getDeviceVersion().catch((err) => { console.warn('[Device] Failed to get version:', err) return null }), bleManager.getDeviceInfo().catch((err) => { console.warn('[Device] Failed to get device info:', err) return null }), ]) console.log('queryDeviceData------------', deviceInfoData) console.log('versionInfo------------', versionInfo) // ✅ 直接使用返回的数据更新本地状态 if (versionInfo) { setVersion(versionInfo.version || '') } if (deviceInfoData) { setDeviceInfo(deviceInfoData) // 计算内存信息 const totalMemory = deviceInfoData.allspace || 0n const availableMemory = deviceInfoData.freespace || 0n const usedMemory = totalMemory - availableMemory setMemoryStats({ total: totalMemory, used: usedMemory, available: availableMemory, }) } } catch (error) { console.error('[Device] Failed to query device data:', error) Toast.show({ title: '获取设备信息失败' }) } finally { setLoading(false) } }, []) const handleOtaUpgrade = async () => { try { if (!binInfo?.fileUrl) { Toast?.show({ title: '固件文件地址无效' }) return } bleStore.setState((prestate) => { return { ...prestate, transferProgress: 0 } }) Toast.showLoading({ renderContent: () => , duration: 0, }) const buffer = await bleManager.performOtaUpgrade(binInfo.fileUrl) Toast.hideLoading() Toast?.show({ title: `固件升级成功 (${buffer.byteLength} bytes) `, duration: 2e3 }) // 升级成功后重新拉取设备数据 queryDeviceData() } catch (error) { console.error('固件升级失败:', error) const msg = typeof error === 'string' ? error : (error as any)?.message || '固件升级失败' Toast?.show({ title: msg }) Toast.hideLoading() } finally { Toast.hide() Toast.hideLoading() } } // ✅ 固件更新处理 const handleFirmwareUpdate = useCallback(async () => { if (!isConnected) { Toast.show({ title: '请先连接设备' }) return } if (!binInfo?.version) { Toast.show({ title: '无法获取固件信息' }) return } Toast.showModal( 确定要更新固件吗? 当前版本:{version} 最新版本:{binInfo?.version} 更新过程中请保持设备连接和电量充足; 更新大概需要 15 分钟;更新过程中请勿操作设备和 APP。 } confirmText="开始更新" onCancel={() => Toast.hideModal()} onConfirm={async () => { Toast.hideModal() Toast.show({ title: '开始更新固件...' }) // TODO: 实现固件更新逻辑 handleOtaUpgrade() }} />, ) }, [isConnected, binInfo, version]) // ✅ 计算内存百分比和显示文本(处理 bigint) const memoryDisplay = useMemo(() => { if (memoryStats.total === 0n) return null const totalMB = Number(memoryStats.total) / 1024 const usedMB = Number(memoryStats.used) / 1024 const availableMB = Number(memoryStats.available) / 1024 const usagePercent = memoryStats.total > 0n ? Math.round((Number(memoryStats.used) / Number(memoryStats.total)) * 100) : 0 return { total: totalMB.toFixed(1), used: usedMB.toFixed(1), available: availableMB.toFixed(1), percent: usagePercent, } }, [memoryStats]) const renderGridItem = useCallback( ({ item }: { item: any }) => { const handleDeleteConfirm = async () => { try { // ✅ 直接使用 Promise 返回值 const result = await bleManager.deleteFile(item) console.log('result--------------', result) if (result.success === 0) { bleStore.removeGalleryItem(item) Toast.hideModal() Toast.show({ title: '删除成功' }) } else { const errorMsg = result.success === 1 ? '删除失败' : result.success === 2 ? '文件不存在' : '未知错误' Toast.show({ title: errorMsg }) } } catch (error: any) { console.error('Error deleting file:', error) Toast.show({ title: error.message || '删除失败' }) } } const handleDelete = async () => { if (!isConnected) { Toast.show({ title: '请先连接设备' }) return } if (galleryList.length <= 1) { Toast.show({ title: '至少保留一个文件' }) return } Toast.showModal( 确定要删除这个文件吗? } onCancel={() => Toast.hideModal()} onConfirm={handleDeleteConfirm} />, ) } const url = buildCdnUrl(item) if (!url) return null return ( {/* 删除按钮 */} { handleDelete() }} > ) }, [itemWidth, isConnected, galleryList.length], ) const renderHeader = () => ( router.back()}> 吧唧管理 ) // ✅ 显示设备信息(名称、品牌、分辨率、电池电量、内存等) const renderDeviceInfo = () => { return ( {/* 标题栏 */} 设备信息 {loading && } {/* 设备基本信息 */} {/* 设备名称 */} {deviceInfo?.name && ( 设备名称 {deviceInfo.name} )} {/* 品牌 */} {deviceInfo?.brand && ( 品牌 {deviceInfo.brand} )} {/* 分辨率 */} {deviceInfo?.size && ( 分辨率 {deviceInfo?.size} )} {deviceInfo?.powerlevel !== undefined && ( 电池电量 50 ? '#22c55e' : deviceInfo.powerlevel > 20 ? '#eab308' : '#ef4444', }} /> {deviceInfo.powerlevel}% )} {/* 设备SN码 */} {sn && ( 设备SN码 {sn} )} {/* 固件版本 */} {version && ( 固件版本 {version} )} {/* 最新固件版本 */} {binInfo?.version && ( 最新固件版本 {binInfo.version} )} {/* 电池电量 */} {/* 固件更新按钮 */} {binInfo?.version && version && binInfo.version !== version && ( 发现新版本固件 {version} → {binInfo.version} 更新 )} {/* 内存信息 */} {memoryDisplay && ( {/* 内存标题 */} 存储空间 {memoryDisplay.percent}% 已使用 {/* 内存进度条 */} {/* 内存详细信息 */} 已使用 {memoryDisplay.used} MB 可用 {memoryDisplay.available} MB 总容量 {memoryDisplay.total} MB )} ) } return ( {renderHeader()} {renderDeviceInfo()} 'row'} ItemSeparatorComponent={() => } keyExtractor={(item) => item} ListEmptyComponent={} numColumns={3} renderItem={renderGridItem} /> ) }) export default Device