324 lines
12 KiB
TypeScript
324 lines
12 KiB
TypeScript
import { Ionicons } from '@expo/vector-icons'
|
||
import { Block, ConfirmModal, ListEmpty, 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 } = bleStore.state
|
||
const { galleryList } = bleStore
|
||
const itemWidth = Math.floor((screenWidth - 12 * 2 - 12 * 2) / 3)
|
||
|
||
// ✅ 本地状态:直接从 Promise 获取数据
|
||
const [deviceInfo, setDeviceInfo] = useState<any>(null)
|
||
const [version, setVersion] = useState<string>('')
|
||
const [memoryStats, setMemoryStats] = useState({
|
||
total: 0n,
|
||
used: 0n,
|
||
available: 0n,
|
||
})
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
// ✅ 页面聚焦时自动获取设备信息和版本号
|
||
useFocusEffect(
|
||
useCallback(() => {
|
||
if (isConnected) {
|
||
queryDeviceData()
|
||
}
|
||
}, [isConnected]),
|
||
)
|
||
|
||
useEffect(() => {
|
||
if (!isConnected) {
|
||
Toast.show({ title: '设备已断开连接' })
|
||
router.back()
|
||
}
|
||
}, [isConnected])
|
||
|
||
// ✅ 直接从 Promise 获取数据
|
||
const queryDeviceData = useCallback(async () => {
|
||
try {
|
||
setLoading(true)
|
||
|
||
// 并行获取设备版本号和设备信息
|
||
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)
|
||
|
||
// ✅ 直接使用返回的数据更新本地状态
|
||
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)
|
||
}
|
||
}, [])
|
||
|
||
// ✅ 计算内存百分比和显示文本(处理 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(
|
||
<ConfirmModal
|
||
title="删除文件"
|
||
content={
|
||
<Text className="text-[14px] font-bold leading-relaxed text-gray-800">确定要删除这个文件吗?</Text>
|
||
}
|
||
onCancel={() => Toast.hideModal()}
|
||
onConfirm={handleDeleteConfirm}
|
||
/>,
|
||
)
|
||
}
|
||
|
||
const url = buildCdnUrl(item)
|
||
if (!url) return null
|
||
|
||
return (
|
||
<Block className="relative">
|
||
<Block
|
||
className={`relative overflow-hidden border-2 ${'shadow-hard-black'} ${'border-black'}`}
|
||
style={{ transform: [{ skewX: '-6deg' }], height: itemWidth, width: itemWidth }}
|
||
>
|
||
<Block className="flex items-center justify-center" style={{ height: itemWidth, width: itemWidth }}>
|
||
<VideoBox style={{ width: itemWidth, height: itemWidth }} url={url} />
|
||
</Block>
|
||
|
||
{/* 删除按钮 */}
|
||
<Block
|
||
className="absolute right-1 top-1 z-20 size-6 items-center justify-center rounded-full border-2 border-black bg-[#e61e25] shadow-hard-black"
|
||
style={{ transform: [{ skewX: '6deg' }] }}
|
||
onClick={() => {
|
||
handleDelete()
|
||
}}
|
||
>
|
||
<Ionicons color="white" name="trash-outline" size={12} />
|
||
</Block>
|
||
</Block>
|
||
</Block>
|
||
)
|
||
},
|
||
[itemWidth, isConnected, galleryList.length],
|
||
)
|
||
|
||
const renderHeader = () => (
|
||
<Block className="flex-row items-center justify-between px-[16px]" style={{ paddingTop: 12, paddingBottom: 12 }}>
|
||
<Block className="ml-[-8px] size-[40px] items-center justify-center" opacity={0.7} onClick={() => router.back()}>
|
||
<Ionicons color="white" name="chevron-back" size={24} />
|
||
</Block>
|
||
<Text className="text-[16px] font-[700] text-white">吧唧管理</Text>
|
||
<Block className="w-[32px]" />
|
||
</Block>
|
||
)
|
||
|
||
// ✅ 显示设备信息(名称、品牌、分辨率、电池电量、内存等)
|
||
const renderDeviceInfo = () => {
|
||
return (
|
||
<Block className="mx-[16px] mb-[12px] rounded-lg border-2 border-black bg-gray-900 p-[12px] shadow-hard-black">
|
||
{/* 标题栏 */}
|
||
<Block className="mb-[8px] flex-row items-center justify-between">
|
||
<Text className="text-[14px] font-bold text-white">设备信息</Text>
|
||
{loading && <ActivityIndicator color="#FFE500" size="small" />}
|
||
</Block>
|
||
|
||
{/* 设备基本信息 */}
|
||
<Block className="mb-[8px] space-y-[8px] border-b border-gray-700 pb-[8px]">
|
||
{/* 设备名称 */}
|
||
{deviceInfo?.name && (
|
||
<Block className="flex-row items-center justify-between">
|
||
<Text className="text-[12px] text-gray-500">设备名称</Text>
|
||
<Text className="text-[12px] text-white">{deviceInfo.name}</Text>
|
||
</Block>
|
||
)}
|
||
|
||
{/* 品牌 */}
|
||
{deviceInfo?.brand && (
|
||
<Block className="flex-row items-center justify-between">
|
||
<Text className="text-[12px] text-gray-500">品牌</Text>
|
||
<Text className="text-[12px] font-bold text-white">{deviceInfo.brand}</Text>
|
||
</Block>
|
||
)}
|
||
|
||
{/* 分辨率 */}
|
||
{deviceInfo?.size && (
|
||
<Block className="flex-row items-center justify-between">
|
||
<Text className="text-[12px] text-gray-500">分辨率</Text>
|
||
<Text className="text-[12px] font-bold text-white">{deviceInfo?.size}</Text>
|
||
</Block>
|
||
)}
|
||
|
||
{/* 固件版本 */}
|
||
{version && (
|
||
<Block className="flex-row items-center justify-between">
|
||
<Text className="text-[12px] text-gray-500">固件版本</Text>
|
||
<Text className="text-[12px] font-bold text-white">{version}</Text>
|
||
</Block>
|
||
)}
|
||
|
||
{/* 电池电量 */}
|
||
{deviceInfo?.powerlevel !== undefined && (
|
||
<Block className="flex-row items-center justify-between">
|
||
<Text className="text-[12px] text-gray-500">电池电量</Text>
|
||
<Block className="flex-row items-center gap-[4px]">
|
||
<Block
|
||
className="h-[4px] w-[20px] rounded-sm bg-gray-600"
|
||
style={{
|
||
backgroundColor:
|
||
deviceInfo.powerlevel > 50 ? '#22c55e' : deviceInfo.powerlevel > 20 ? '#eab308' : '#ef4444',
|
||
}}
|
||
/>
|
||
<Text className="text-[12px] font-bold text-white">{deviceInfo.powerlevel}%</Text>
|
||
</Block>
|
||
</Block>
|
||
)}
|
||
</Block>
|
||
|
||
{/* 内存信息 */}
|
||
{memoryDisplay && (
|
||
<Block className="space-y-[8px]">
|
||
{/* 内存标题 */}
|
||
<Block className="mb-[8px] flex-row items-center justify-between">
|
||
<Text className="text-[12px] font-bold text-white">存储空间</Text>
|
||
<Text className="text-[11px] text-gray-400">{memoryDisplay.percent}% 已使用</Text>
|
||
</Block>
|
||
|
||
{/* 内存进度条 */}
|
||
<Block className="h-[6px] overflow-hidden rounded-full bg-gray-700">
|
||
<Block
|
||
className="h-full bg-yellow-400 bg-gradient-to-r"
|
||
style={{
|
||
height: 6,
|
||
width: `${memoryDisplay.percent}%`,
|
||
}}
|
||
/>
|
||
</Block>
|
||
|
||
{/* 内存详细信息 */}
|
||
<Block className="mt-[8px] flex-row justify-between gap-[8px]">
|
||
<Block className="flex-1">
|
||
<Text className="mb-[2px] text-[10px] text-gray-500">已使用</Text>
|
||
<Text className="text-[12px] font-bold text-yellow-400">{memoryDisplay.used} MB</Text>
|
||
</Block>
|
||
<Block className="flex-1">
|
||
<Text className="mb-[2px] text-[10px] text-gray-500">可用</Text>
|
||
<Text className="text-[12px] font-bold text-green-400">{memoryDisplay.available} MB</Text>
|
||
</Block>
|
||
<Block className="flex-1">
|
||
<Text className="mb-[2px] text-[10px] text-gray-500">总容量</Text>
|
||
<Text className="text-[12px] font-bold text-white">{memoryDisplay.total} MB</Text>
|
||
</Block>
|
||
</Block>
|
||
</Block>
|
||
)}
|
||
</Block>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<Block className="relative flex-1 bg-black">
|
||
<BannerSection />
|
||
<Stack.Screen options={{ headerShown: false }} />
|
||
{renderHeader()}
|
||
|
||
<Block className="z-10 flex-1">
|
||
{renderDeviceInfo()}
|
||
<FlashList
|
||
contentContainerStyle={{ paddingHorizontal: 12, paddingBottom: 200 }}
|
||
data={galleryList}
|
||
drawDistance={1200}
|
||
// @ts-ignore
|
||
estimatedItemSize={itemWidth}
|
||
getItemType={() => 'row'}
|
||
ItemSeparatorComponent={() => <Block style={{ height: 6 }} />}
|
||
keyExtractor={(item) => item}
|
||
ListEmptyComponent={<ListEmpty />}
|
||
numColumns={3}
|
||
renderItem={renderGridItem}
|
||
/>
|
||
</Block>
|
||
</Block>
|
||
)
|
||
})
|
||
|
||
export default Device
|