From 14c84de1ce5b000e1c578bfc274c217990a6e42b Mon Sep 17 00:00:00 2001 From: km2025 Date: Fri, 23 Jan 2026 14:21:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8D=A0=E4=BD=8D?= =?UTF-8?q?=E5=9B=BE=E6=94=AF=E6=8C=81=EF=BC=8C=E4=BC=98=E5=8C=96=20Img=20?= =?UTF-8?q?=E5=92=8C=20Video=20=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20OTA=20=E5=8D=87=E7=BA=A7=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- @share/components/Img.tsx | 84 +++++++++++++++++++++++++++---------- @share/components/Video.tsx | 61 ++++++++++++++++++++------- app.config.js | 2 +- app/(tabs)/explore.tsx | 20 +++++++-- app/(tabs)/index.tsx | 8 +++- app/(tabs)/sync.tsx | 4 +- app/pointList.native.tsx | 4 +- ble/managers/bleManager.ts | 9 +++- ble/protocol/Constants.ts | 2 +- 9 files changed, 143 insertions(+), 51 deletions(-) diff --git a/@share/components/Img.tsx b/@share/components/Img.tsx index 4dcbd42..1b9a12f 100644 --- a/@share/components/Img.tsx +++ b/@share/components/Img.tsx @@ -1,5 +1,6 @@ import { Image as ExpoImage, type ImageProps as ExpoImageProps } from 'expo-image' -import React, { forwardRef, memo, useMemo } from 'react' +import React, { forwardRef, memo, useEffect, useMemo, useState } from 'react' +import { View } from 'react-native' import tw from 'twrnc' interface ImgProps extends ExpoImageProps { @@ -11,6 +12,8 @@ interface ImgProps extends ExpoImageProps { isCompression?: boolean /** 自定义缓存键,用于需要重定向的 URL */ cacheKey?: string + /** 占位图 URL,在真实图片加载完成前显示 */ + placeholderSrc?: string } // ExpoImage.clearDiskCache() @@ -24,15 +27,22 @@ const Img = forwardRef((props, ref) => { className = '', style = {}, src, - errorSource, source: propSource, cacheKey, width = 256, isCompression = false, isWebP = true, + placeholderSrc, + onLoad, ...reset } = props + const [isLoaded, setIsLoaded] = useState(false) + + useEffect(() => { + setIsLoaded(false) + }, [src]) + const imageStyle = tw`${className}` // 静态图全部压缩jpg @@ -49,47 +59,77 @@ const Img = forwardRef((props, ref) => { // 构建图片源 const imageSource = useMemo(() => { - // 如果提供了source属性,优先使用 if (propSource) return propSource - if (!src) return undefined if (typeof src === 'number') { - // 本地图片资源(require导入的资源ID) return src } else { - // 网络图片或本地文件路径 if (isNetworkImage(src)) { const finalUrl = isCompression ? compressionUrl : src - // console.log('finalUrl-------------', finalUrl) - return { uri: finalUrl, cacheKey: cacheKey || finalUrl, } } else { - // 本地文件路径 return { uri: src } } } }, [src, propSource, cacheKey, isCompression, compressionUrl]) - const imgProps = { - style: [style, imageStyle], - ref, - source: imageSource, - // 使用 disk 缓存策略,减少内存占用 - cachePolicy: 'disk' as const, - // 添加内存缓存上限,当内存紧张时优先释放 - recyclingKey: typeof src === 'string' ? src : undefined, - errorSource, - transition: { duration: 200, effect: 'cross-dissolve' as const }, - ...reset, + const handleLoad = (e: any) => { + console.log('handleLoad--------------', e) + + setIsLoaded(true) + onLoad?.(e) } - return + // 无占位图时直接返回原图 + if (!placeholderSrc) { + return ( + + ) + } - // return + const showPlaceholder = placeholderSrc + + // 有占位图时使用层叠布局 + return ( + + {/* 占位图层 - 加载完成后隐藏 */} + {showPlaceholder && ( + + )} + + {/* 真实图片层 */} + + + ) }) Img.displayName = 'Img' diff --git a/@share/components/Video.tsx b/@share/components/Video.tsx index f6a378e..3b71854 100644 --- a/@share/components/Video.tsx +++ b/@share/components/Video.tsx @@ -1,12 +1,15 @@ -import { Image, type ImageRef } from 'expo-image' +import { Image as ExpoImage, type ImageRef } from 'expo-image' import { memo, useEffect, useRef, useState } from 'react' -import { type ViewStyle } from 'react-native' +import { View, type ViewStyle } from 'react-native' import Video from 'react-native-video' +import tw from 'twrnc' import { videoUrlCache } from '@/utils/storage' type Props = { + className?: string url?: string + placeholderUrl?: string needWeb?: boolean style?: ViewStyle width?: number @@ -14,7 +17,16 @@ type Props = { } & React.ComponentProps // 默认宽度256半屏宽度 -const VideoBox = ({ url, needWeb = true, width = 256, style, autoplay = true, ...videoProps }: Props) => { +const VideoBox = ({ + className = '', + url, + placeholderUrl = '', + needWeb = true, + width = 256, + style, + autoplay = true, + ...videoProps +}: Props) => { const [urlFinal, setUrlFinal] = useState('') const imageRef = useRef(null) @@ -114,7 +126,7 @@ const VideoBox = ({ url, needWeb = true, width = 256, style, autoplay = true, .. ) } return ( - + + {/* 占位图层 - 加载完成后隐藏 */} + {showPlaceholder && ( + + )} + + {/* 真实图片层 */} + + ) } diff --git a/app.config.js b/app.config.js index 329d25d..bf671fb 100644 --- a/app.config.js +++ b/app.config.js @@ -10,7 +10,7 @@ export const IOS_UNIVERSAL_LINK = 'duooomi.bowong.cn' // 原生版本,原生代码变更时需要更新此版本号 export const VERSION = '1.2.0' // JavaScript版本,JS代码变更时需要更新此版本号 -export const APP_VERSION = 'dev202601211414' +export const APP_VERSION = 'dev202601221539' const ALIPAY_SCHEMA = 'alipay2021006119657394' const ALIPAY_SCHEMA_SANDBOX = 'alipay9021000158673972' diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/explore.tsx index 88d2551..d73aac5 100644 --- a/app/(tabs)/explore.tsx +++ b/app/(tabs)/explore.tsx @@ -61,6 +61,7 @@ export default observer(function TabTwoScreen() { const [otaUrl, setOtaUrl] = useState( userStore.scannedQR ?? 'https://cdn.roasmax.cn/upload/bf25206ab8644a8fb914aad5cf0fca08.bin', ) + const [comType, setComType] = useState('0x02') // 默认OTA_PACKAGE类型为128 useEffect(() => { if (userStore.scannedQR) { @@ -138,10 +139,10 @@ export default observer(function TabTwoScreen() { renderContent: () => , duration: 0, }) - const buffer = await bleManager.performOtaUpgrade(otaUrl, (progress) => { - // 进度在 bleStore.transferProgress 中同步显示,这里仅可选处理 - }) - Toast?.show({ title: `OTA升级完成 (${buffer.byteLength} bytes) ` }) + const buffer = await bleManager.performOtaUpgrade(otaUrl, comType) + + Toast.hide() + Toast?.show({ title: `OTA升级完成 (${buffer.byteLength} bytes) `, duration: 2e3 }) } catch (error) { console.error('OTA upgrade failed:', error) const msg = typeof error === 'string' ? error : (error as any)?.message || 'OTA升级失败' @@ -426,6 +427,17 @@ export default observer(function TabTwoScreen() { /> + + 蓝牙头设置 Command Type: + + + OTA URL: (function HeroCircle({ selectedItem, const Width = 216 const previewUrl = selectedItem?.webpHighPreviewUrl || selectedItem?.webpPreviewUrl || '' + const placeholderUrl = selectedItem?.webpPreviewUrl || '' return ( @@ -605,7 +606,12 @@ const HeroCircle = observer(function HeroCircle({ selectedItem, - + diff --git a/app/(tabs)/sync.tsx b/app/(tabs)/sync.tsx index 777aff8..ca0a958 100644 --- a/app/(tabs)/sync.tsx +++ b/app/(tabs)/sync.tsx @@ -956,7 +956,7 @@ const TopCircleSection = memo( const GalleryRenderer = memo(({ selectedItem }: { selectedItem: any }) => { const url = selectedItem?.url || selectedItem?.imageUrl - + const placeholderUrl = selectedItem?.imageUrl // console.log('GalleryRenderer--------------', selectedItem) if (!url) return null @@ -967,7 +967,7 @@ const GalleryRenderer = memo(({ selectedItem }: { selectedItem: any }) => { className="relative z-10 border-4 border-black" style={{ width: Width, height: Width, borderRadius: Width, overflow: 'hidden' }} > - + ) }) diff --git a/app/pointList.native.tsx b/app/pointList.native.tsx index 3c57536..7987cbf 100644 --- a/app/pointList.native.tsx +++ b/app/pointList.native.tsx @@ -84,8 +84,8 @@ const ChargePage = observer(function ChargePage() { const { data, error } = await handleError( async () => await alipay.preRecharge({ - credits: 2, - // credits: parseInt(selectedRecharge?.points.replace(/,/g, '')), + // credits: 2, + credits: parseInt(selectedRecharge?.points.replace(/,/g, '')), }), ) diff --git a/ble/managers/bleManager.ts b/ble/managers/bleManager.ts index ad5e5b4..78a4167 100644 --- a/ble/managers/bleManager.ts +++ b/ble/managers/bleManager.ts @@ -649,7 +649,11 @@ class BleManager { } } - async performOtaUpgrade(url: string, onProgress?: (progress: number) => void): Promise { + async performOtaUpgrade( + url: string, + comType = '0x02', + onProgress?: (progress: number) => void, + ): Promise { const state = bleStore.state if (!state.connectedDevice) { const error = 'No device connected' @@ -687,7 +691,8 @@ class BleManager { await this.fileTransferService.transferFile( state.connectedDevice.id, arrayBuffer, - COMMAND_TYPES.OTA_PACKAGE, + // COMMAND_TYPES.OTA_PACKAGE, + Number(comType), (progress) => { bleStore.setState((prev) => ({ ...prev, transferProgress: progress * 100 })) onProgress?.(progress) diff --git a/ble/protocol/Constants.ts b/ble/protocol/Constants.ts index 8842713..706fd25 100644 --- a/ble/protocol/Constants.ts +++ b/ble/protocol/Constants.ts @@ -35,7 +35,7 @@ export const COMMAND_TYPES = { PREPARE_TRANSFER: 0x14, } as const -export type APP_COMMAND_TYPES = (typeof COMMAND_TYPES)[keyof typeof COMMAND_TYPES] +export type APP_COMMAND_TYPES = (typeof COMMAND_TYPES)[keyof typeof COMMAND_TYPES] | number export const EVENT_TYPES = { TRANSFER_OTA_PACKAGE: {