/* eslint-disable react/display-name */ import React, { useMemo, useState, useEffect, useRef, memo, useCallback } from 'react' import { Block, ConfirmModal, Text, Toast, VideoBox } from '@share/components' import Img from '@share/components/Img' import { AntDesign, FontAwesome, Ionicons, MaterialCommunityIcons } from '@expo/vector-icons' import * as ImagePicker from 'expo-image-picker' import { Dimensions, ScrollView, View } from 'react-native' import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { imgPicker } from '@/@share/apis' import { useBleExplorer } from '@/ble' import { cn } from '@/utils/cn' import { screenHeight, screenWidth } from '@/utils' import { FlashList } from '@shopify/flash-list' import { useTemplateGenerations } from '@/hooks/data/use-template-generations' import { useFileUpload } from '@/hooks/actions/use-file-upload' import { useTemplateActions } from '@/hooks/actions/use-template-actions' import { useAuth } from '@/hooks/core/use-auth' import { aniStorage } from '@/utils/aniStorage' import { get } from 'react-native/Libraries/TurboModule/TurboModuleRegistry' // ============ 常量定义 ============ const BACKGROUND_VIDEOS = [ 'https://cdn.roasmax.cn/upload/529c4a4ff1db46a9bd9b4da9af1e7eae.mp4', 'https://cdn.roasmax.cn/upload/63c9f4a73a354403bb33f680d7939538.mp4', 'https://cdn.roasmax.cn/upload/60c10adad4364f319c1139bfed719cda.mp4', 'https://cdn.roasmax.cn/upload/75adb7c620274200bf30950ee4af9a95.mp4', 'https://cdn.roasmax.cn/upload/c6d501f816a24354ab652d2b9083a32b.mp4', ] const bgGif = require('@/assets/images/bg1.gif') // ============ 工具函数 ============ function isVideoUrl(url: string) { return url?.endsWith('.mp4') } // ============ 小组件 ============ const SpinningLoader = memo(() => { const rotation = useSharedValue(0) useEffect(() => { rotation.value = withRepeat(withTiming(360, { duration: 1000, easing: Easing.linear }), -1, false) }, []) const animatedStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${rotation.value}deg` }], })) return ( ) }) const DeviceItem = memo(({ device, onConnectToggle }: { device: any; onConnectToggle: (device: any) => void }) => { const { name = 'Unknown Device', id, connected: isConnected } = device return ( {isConnected && } {name} {isConnected ? '已连接' : '未连接'} onConnectToggle(device)} className={`h-[40px] items-center justify-center border-[3px] border-black px-[16px] text-[12px] font-[900] italic ${isConnected ? 'bg-accent text-black shadow-medium-black' : 'bg-white text-black shadow-medium-gray'}`} > {isConnected ? '已连接' : '连接'} ) }) const GridItem = memo( ({ post, isSelected, isSelectionMode, itemWidth, isVisible, onSelect, }: { post: any isSelected: boolean isSelectionMode: boolean itemWidth: number isVisible: boolean onSelect: (post: any) => void }) => { return ( onSelect(post)} className="relative"> {/* {isVideoUrl(post.imageUrl) ? ( ) : ( )} */} {isSelected && } {isSelectionMode && ( )} ) }, ) const FilterButtons = memo(({ isSelectionMode, onToggleSelectionMode }: { isSelectionMode: boolean; onToggleSelectionMode: () => void }) => { return ( {['我的生成'].map((label) => { return ( {label} ) })} {isSelectionMode ? '取消' : '管理'} ) }) const SelectionBar = memo( ({ isSelectionMode, selectedCount, onSelectAll, onDelete, }: { isSelectionMode: boolean selectedCount: number onSelectAll: () => void onDelete: () => void }) => { const insets = useSafeAreaInsets() if (!isSelectionMode) return null return ( 已选: {selectedCount} 全选 0 ? 'bg-[#e61e25] text-white' : 'bg-gray-200 text-gray-400'}`} > {selectedCount > 0 ? '删除' : '删除'} ) }, ) const FABButtons = memo(({ onGenAgain, onSync, canSync }: { onGenAgain: () => void; onSync: () => void; canSync: boolean }) => { const insets = useSafeAreaInsets() return ( 再次生成 同步 ) }) // ============ 主要组件部分 ============ const HeaderBanner = memo(({ connectedDevice, onPick }: { connectedDevice: any; onPick: () => void }) => { return ( {connectedDevice ? '设备已连接' : '设备离线'} 上传本地 ) }) const TopCircleSection = memo( ({ connectedDevice, onStartConnect, outerRingStyle, selectedItem, }: { connectedDevice: any onStartConnect: () => void outerRingStyle: any selectedItem: any }) => { return ( {connectedDevice ? '已连接' : '选择设备'} {connectedDevice && ( {/* */} 离线模式 )} ) }, ) const GalleryRenderer = memo(({ selectedItem }: { selectedItem: any }) => { const uri = selectedItem?.url || selectedItem.imageUrl console.log('GalleryRenderer------------', uri) if (!uri) return null const Width = 256 if (isVideoUrl(uri)) { return ( ) } if (selectedItem?.type?.includes('video')) { return } return }) const ManagerView = memo( ({ viewState, onBack, onConnectToggle }: { viewState: string; onBack: () => void; onConnectToggle: (device: any) => void }) => { const { discoveredDevices } = useBleExplorer() if (viewState !== 'manager') return null return ( 设备管理 {/* 空占位符 */} {discoveredDevices .filter((i) => i.name) .map((device) => ( ))} ) }, ) const BackgroundBanner = memo(({ selectedItem }: { selectedItem: any }) => { const uri = selectedItem?.url || selectedItem.imageUrl // if (!uri) return null console.log('BackgroundBanner----', uri) // if (isVideoUrl(uri)) { // return ( // // // // // ) // } return ( {/* */} ) }) // ============ 主组件 ============ const Sync = () => { const { user } = useAuth() const { data: generationsData, loading: generationsLoading, load: loadGenerations } = useTemplateGenerations() const { uploadFile, loading: uploadLoading } = useFileUpload() const { runTemplate, batchDeleteGenerations, loading: actionLoading } = useTemplateActions() const [viewState, setViewState] = useState<'home' | 'manager' | 'scanner'>('home') const [selectedItem, setSelectedItem] = useState({} as any) const [isSelectionMode, setIsSelectionMode] = useState(false) const [selectedIds, setSelectedIds] = useState>(new Set()) const { connectedDevice, startScan, stopScan, connectToDevice, disconnectDevice, transferMediaSingle, } = useBleExplorer() const outerRotate = useSharedValue(0) const itemWidth = Math.floor((screenWidth - 12 * 2 - 12 * 2) / 3) // 加载生成记录 useEffect(() => { if (user?.id) { loadGenerations({ userId: user.id }) } }, [user?.id, loadGenerations]) // 将生成记录转换为 posts 格式 const posts = useMemo(() => { const generations = generationsData?.data || [] return generations.map((gen: any) => ({ id: gen.id, imageUrl: Array.isArray(gen.resultUrl) ? gen.resultUrl[0] : gen.resultUrl, originalUrl: gen.originalUrl, templateId: gen.templateId, type: gen.type, status: gen.status, createdAt: gen.createdAt, title: `生成-${gen.id.slice(0, 6)}`, rank: 'S', author: user?.name || 'User', avatarUrl: user?.image || 'https://image.pollinations.ai/prompt/cool%20anime%20boy%20avatar%20hoodie?seed=123&nologo=true', })) }, [generationsData, user]) const viewableIds = useRef>(new Set(posts.map((p: any) => p.id))) // 动画效果 useEffect(() => { outerRotate.value = withRepeat(withTiming(360, { duration: 12000, easing: Easing.linear }), -1, false) }, []) const outerRingStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${outerRotate.value}deg` }], })) // 事件处理函数 const handleConnectToggle = useCallback( async (item: any) => { if (item.connected) { disconnectDevice() } else { await disconnectDevice() Toast.showLoading({ title: '连接中...', duration: 30e3, }) connectToDevice(item) .then(() => { console.log('设备连接成功') }) .catch(() => { Toast.show({ title: '设备连接失败', }) }) .finally(() => { Toast.hideLoading() }) } }, [connectToDevice, disconnectDevice], ) const canSync = useMemo(() => { return !!connectedDevice?.id && !!selectedItem?.imageUrl }, [connectedDevice, selectedItem]) const handleSync = useCallback(async () => { // await aniStorage.set('test_url', { test: 'data' }) console.log( 'aniStorage.has----------------', await aniStorage.has('test_url'), // await aniStorage.delete('test_url'), // await aniStorage.get('test_url'), ) if (!canSync) { Toast.show({ title: '请先连接设备' }) return } Toast.show({ renderContent: () => ( 正在同步文件... ), duration: 0, }) transferMediaSingle(selectedItem?.imageUrl) .then(() => { Toast.show({ title: '同步成功' }) }) .catch(() => { Toast.hideLoading() Toast.show({ title: '同步失败' }) }) }, [canSync, selectedItem, transferMediaSingle]) const handlePick = useCallback(async () => { const assetList = await imgPicker({ maxImages: 1, type: ImagePicker.MediaTypeOptions.All, resultType: 'asset' }) if (!assetList?.length) return const asset = assetList[0] const isVideo = typeof asset === 'object' && asset?.type === 'video' const uri = typeof asset === 'object' ? asset?.uri : asset Toast.showLoading({ title: '上传中...', duration: 30e3 }) // 上传到云端 const fileBlob = await fetch(uri).then((r) => r.blob()) const mimeType = fileBlob.type || (isVideo ? 'video/mp4' : 'image/jpeg') const file = new File([fileBlob], fileName, { type: mimeType }) const { url, error } = await uploadFile(file) Toast.hideLoading() if (error || !url) { Toast.show({ title: '上传失败' }) return } const newItem = { id: `local-${Date.now()}`, type: isVideo ? 'video' : 'image', imageUrl: url, url, originalUrl: url, ...(typeof asset === 'object' ? asset : {}), } setSelectedItem(newItem) // // 如果设备已连接,询问是否同步 // 先预览不直接同步上传 // if (connectedDevice?.id) { // Toast.showModal( // 文件已上传,是否立即同步到设备?} // onConfirm={() => { // Toast.hideModal() // handleSync() // }} // onCancel={() => Toast.hideModal()} // />, // ) // } else { // Toast.show({ title: '上传成功' }) // } }, [uploadFile, connectedDevice, handleSync]) const startConnect = useCallback(() => { setViewState('manager') startScan() }, [startScan]) const handleGenAgain = useCallback(() => { if (!selectedItem?.templateId) { Toast.show({ title: '请先选择一个生成记录' }) return } Toast.showModal( 生成同款风格将消耗 2 Goo 算力。 } onConfirm={handleGenAgainConfirm} onCancel={() => Toast.hideModal()} />, ) }, [selectedItem]) const handleGenAgainConfirm = useCallback(async () => { if (!selectedItem?.templateId || !selectedItem?.originalUrl) return Toast.hideModal() Toast.show({ renderContent: () => ( 正在生成中... ), duration: 0, }) const { generationId, error } = await runTemplate({ templateId: selectedItem.templateId, data: {}, originalUrl: selectedItem.originalUrl, }) Toast.hideLoading() if (error || !generationId) { Toast.show({ title: error?.message || '生成失败' }) return } Toast.show({ renderContent: () => ( 生成成功 ), }) // 刷新列表 if (user?.id) { loadGenerations({ userId: user.id }) } }, [selectedItem, runTemplate, user?.id, loadGenerations]) const toggleSelectionMode = useCallback(() => { setIsSelectionMode((v) => !v) setSelectedIds(new Set()) }, []) const handleItemSelect = useCallback( (post: any) => { if (isSelectionMode) { const next = new Set(selectedIds) if (next.has(post.id)) next.delete(post.id) else next.add(post.id) setSelectedIds(next) } else { setSelectedItem(post) } }, [isSelectionMode, selectedIds], ) const handleDelete = useCallback(async () => { if (selectedIds.size === 0) return const { success, error } = await batchDeleteGenerations(Array.from(selectedIds)) if (error || !success) { Toast.show({ title: error?.message || '删除失败' }) return } Toast.show({ title: `成功删除 ${selectedIds.size} 个生成记录` }) // 刷新列表 if (user?.id) { await loadGenerations({ userId: user.id }) } setSelectedIds(new Set()) setIsSelectionMode(false) }, [selectedIds, batchDeleteGenerations, user?.id, loadGenerations]) const selectAll = useCallback(() => { if (selectedIds.size === posts.length) { setSelectedIds(new Set()) } else { setSelectedIds(new Set(posts.map((p: any) => p.id))) } }, [posts, selectedIds.size]) const onViewableItemsChanged = useCallback((params: any) => { const { viewableItems } = params viewableIds.current = new Set(viewableItems.map((v: any) => v.item.id)) }, []) const renderHeaderFlatList = useMemo( () => ( ), [connectedDevice, handlePick, startConnect, outerRingStyle, isSelectionMode, toggleSelectionMode, selectedItem], ) const renderGridItem = useCallback( ({ item: post }: { item: any }) => { const isSelected = isSelectionMode ? selectedIds.has(post.id) : selectedItem?.id === post.id const isVisible = viewableIds.current.has(post.id) return ( ) }, [isSelectionMode, selectedIds, selectedItem, itemWidth, handleItemSelect], ) return ( 'row'} removeClippedSubviews keyExtractor={(item: any) => item?.id} ListHeaderComponent={renderHeaderFlatList} renderItem={renderGridItem} ItemSeparatorComponent={() => } contentContainerStyle={{ paddingHorizontal: 12, paddingBottom: 200 }} viewabilityConfig={{ itemVisiblePercentThreshold: 60 }} onViewableItemsChanged={onViewableItemsChanged} /> { setViewState('home') stopScan() }} onConnectToggle={handleConnectToggle} /> ) } export default memo(Sync)