Compare commits

..

No commits in common. "92f57595f0167c02c5572548ed47faa0d5b865f8" and "3d741441e271980e230097d52c87aefb0b6f0689" have entirely different histories.

4 changed files with 63 additions and 90 deletions

View File

@ -52,7 +52,7 @@ const Img = forwardRef<ExpoImage, ImgProps>((props, ref) => {
const imgProps = { const imgProps = {
style: [style, imageStyle], style: [style, imageStyle],
ref, ref,
// placeholder: blurhash, placeholder: blurhash,
source: imageSource, source: imageSource,
cachePolicy: 'memory-disk' as const, cachePolicy: 'memory-disk' as const,
errorSource, errorSource,

View File

@ -1,5 +1,5 @@
import { Image } from 'expo-image' import { Image } from 'expo-image'
import { memo, useEffect, useMemo, useState } from 'react' import { memo, useEffect, useState } from 'react'
import { ViewStyle } from 'react-native' import { ViewStyle } from 'react-native'
import Video from 'react-native-video' import Video from 'react-native-video'
@ -7,58 +7,19 @@ type Props = {
url?: string url?: string
poster?: string poster?: string
style?: ViewStyle style?: ViewStyle
width?: number
} & React.ComponentProps<typeof Video> } & React.ComponentProps<typeof Video>
const VideoBox = ({ url, poster, width = 120, style, ...videoProps }: Props) => { const VideoBox = ({ url, poster, style, ...videoProps }: Props) => {
const [urlFinal, setUrlFinal] = useState('') const [paused, setPaused] = useState(true)
const createUrl = (url: string) => {
return `https://modal-dev.bowong.cc/api/custom/video/converter/${encodeURI(url)}?options=compression_level=3,quality=70,loop=true,resolution=${width}x${width},fps=24`
}
const isImg = (url: any) => {
if (!url) return false
const lowerUrl = url.toLowerCase()
return lowerUrl?.match(/\.(jpg|jpeg|png|gif|webp|bmp|tiff|svg)(\?.*)?$/i)
}
async function resolveRedirect(url: string) {
const res = await fetch(url, {
method: 'GET',
headers: {
Range: 'bytes=0-0',
},
})
return res.url
}
const setRedirectUrl = async (url?: string) => {
const isImg2 = isImg(url)
if (isImg2) {
setUrlFinal(url!)
return
}
const webpUrl = createUrl(url!)
const finalUrl = await resolveRedirect(webpUrl)
// console.log('finalUrl-----------', finalUrl)
setUrlFinal(finalUrl!)
}
useEffect(() => { useEffect(() => {
if (!url) return console.log('url--------', url);
setRedirectUrl(url!)
// const finalUrl = createUrl(url)
// console.log('finalUrl-----------', finalUrl)
// setUrlFinal(finalUrl) setPaused(!Boolean(url))
return
}, [url]) }, [url])
// console.log('urlFinal--------- ', urlFinal, url) const createUrl = (url: string)=>`https://modal-dev.bowong.cc/api/custom/video/converter/${encodeURI(url)}?options=compression_level=3,quality=70,loop=true,resolution=120x120,fps=24`
if (!url) return null
return ( return (
// 当 url 变化时通过 key 强制重载,确保自动播放生效 // 当 url 变化时通过 key 强制重载,确保自动播放生效
// <Video // <Video
@ -75,7 +36,7 @@ const VideoBox = ({ url, poster, width = 120, style, ...videoProps }: Props) =>
// style={style as any} // style={style as any}
// {...videoProps} // {...videoProps}
// /> // />
<Image key={urlFinal} cachePolicy="memory-disk" source={{ uri: urlFinal }} style={style as any} /> <Image source={{ uri: url ? createUrl(url) : '' }} style={style as any} />
) )
} }

View File

@ -4,7 +4,7 @@ import { Block, ConfirmModal, Text, Toast, VideoBox } from '@share/components'
import Img from '@share/components/Img' import Img from '@share/components/Img'
import { AntDesign, FontAwesome, Ionicons, MaterialCommunityIcons } from '@expo/vector-icons' import { AntDesign, FontAwesome, Ionicons, MaterialCommunityIcons } from '@expo/vector-icons'
import * as ImagePicker from 'expo-image-picker' import * as ImagePicker from 'expo-image-picker'
import { Dimensions, Platform, ScrollView, View } from 'react-native' import { Dimensions, ScrollView, View } from 'react-native'
import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated' import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'
import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { imgPicker } from '@/@share/apis' import { imgPicker } from '@/@share/apis'
@ -17,7 +17,7 @@ import { useFileUpload } from '@/hooks/actions/use-file-upload'
import { useTemplateActions } from '@/hooks/actions/use-template-actions' import { useTemplateActions } from '@/hooks/actions/use-template-actions'
import { useAuth } from '@/hooks/core/use-auth' import { useAuth } from '@/hooks/core/use-auth'
import { aniStorage } from '@/utils/aniStorage' import { aniStorage } from '@/utils/aniStorage'
import * as FileSystem from 'expo-file-system' import { get } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'
// ============ 常量定义 ============ // ============ 常量定义 ============
const BACKGROUND_VIDEOS = [ const BACKGROUND_VIDEOS = [
@ -115,6 +115,11 @@ const GridItem = memo(
style={{ transform: [{ skewX: '-6deg' }], height: itemWidth, width: itemWidth }} style={{ transform: [{ skewX: '-6deg' }], height: itemWidth, width: itemWidth }}
> >
<Block style={{ height: itemWidth, width: itemWidth }}> <Block style={{ height: itemWidth, width: itemWidth }}>
{/* {isVideoUrl(post.imageUrl) ? (
<VideoBox url={post.imageUrl} style={{ height: itemWidth, width: itemWidth }} />
) : (
<Img src={post.imageUrl} className="h-full w-full" />
)} */}
<Img src={post.imageUrl} className="h-full w-full" /> <Img src={post.imageUrl} className="h-full w-full" />
</Block> </Block>
{isSelected && <Block className="absolute inset-[0px] border-[3px] border-accent" />} {isSelected && <Block className="absolute inset-[0px] border-[3px] border-accent" />}
@ -288,17 +293,23 @@ const TopCircleSection = memo(
) )
const GalleryRenderer = memo(({ selectedItem }: { selectedItem: any }) => { const GalleryRenderer = memo(({ selectedItem }: { selectedItem: any }) => {
const url = selectedItem?.url || selectedItem.imageUrl const uri = selectedItem?.url || selectedItem.imageUrl
console.log('GalleryRenderer------------', url) console.log('GalleryRenderer------------', uri)
if (!url) return null if (!uri) return null
const Width = 256 const Width = 256
return ( if (isVideoUrl(uri)) {
<View style={{ width: Width, height: Width, borderRadius: Width, overflow: 'hidden' }} className="relative z-[10] border-[4px] border-black"> return (
<VideoBox width={Width} url={url} style={{ width: Width, height: Width }} /> <View style={{ width: Width, height: Width, borderRadius: Width, overflow: 'hidden' }} className="relative z-[10] border-[4px] border-black">
</View> <VideoBox url={uri} style={{ width: Width, height: Width }} />
) </View>
)
}
if (selectedItem?.type?.includes('video')) {
return <VideoBox url={uri} style={{ height: 256, width: 256 }} />
}
return <Img src={uri} className="h-full w-full" />
}) })
const ManagerView = memo( const ManagerView = memo(
@ -335,11 +346,26 @@ const ManagerView = memo(
) )
const BackgroundBanner = memo(({ selectedItem }: { selectedItem: any }) => { const BackgroundBanner = memo(({ selectedItem }: { selectedItem: any }) => {
const url = selectedItem?.url || selectedItem.imageUrl const uri = selectedItem?.url || selectedItem.imageUrl
console.log('BackgroundBanner------------', url) // if (!uri) return null
console.log('BackgroundBanner----', uri)
// if (isVideoUrl(uri)) {
// return (
// <Block className="absolute inset-0 bottom-0 left-0 right-0 top-0 z-[0] bg-red-400">
// <VideoBox
// url={'https://cdn.roasmax.cn/upload/c6d501f816a24354ab652d2b9083a32b.mp4'}
// viewType={1}
// style={{ width: screenWidth, height: screenHeight }}
// />
// <Block className="absolute inset-0 bg-black/0" />
// </Block>
// )
// }
return ( return (
<Block className="absolute inset-0 bottom-0 left-0 right-0 top-0 z-[10] overflow-hidden"> <Block className="absolute inset-0 bottom-0 left-0 right-0 top-0 z-[10] overflow-hidden">
<VideoBox width={512} url={url} style={{ width: screenWidth, height: screenHeight }} /> {/* <VideoBox url={BACKGROUND_VIDEOS[0]} style={{ width: screenWidth, height: screenHeight }} /> */}
<Img src={bgGif} style={{ width: screenWidth, height: screenHeight }} />
</Block> </Block>
) )
}) })
@ -395,20 +421,6 @@ const Sync = () => {
})) }))
}, [generationsData, user]) }, [generationsData, user])
useEffect(() => {
if (!selectedItem?.id && posts.length > 0) {
const firstItem = posts[0]
const newItem = {
id: firstItem.id,
imageUrl: firstItem.imageUrl,
url: firstItem.imageUrl,
originalUrl: firstItem.originalUrl,
}
setSelectedItem(newItem)
}
}, [posts])
const viewableIds = useRef<Set<string>>(new Set(posts.map((p: any) => p.id))) const viewableIds = useRef<Set<string>>(new Set(posts.map((p: any) => p.id)))
// 动画效果 // 动画效果
@ -493,23 +505,23 @@ const Sync = () => {
const handlePick = useCallback(async () => { const handlePick = useCallback(async () => {
const assetList = await imgPicker({ maxImages: 1, type: ImagePicker.MediaTypeOptions.All, resultType: 'asset' }) const assetList = await imgPicker({ maxImages: 1, type: ImagePicker.MediaTypeOptions.All, resultType: 'asset' })
if (!assetList?.length) return if (!assetList?.length) return
const result = assetList[0] as any const asset = assetList[0]
const isVideo = typeof asset === 'object' && asset?.type === 'video'
const uri = typeof asset === 'object' ? asset?.uri : asset
const fileName = typeof asset === 'object' && asset?.fileName ? asset.fileName : `upload_${Date.now()}.${isVideo ? 'mp4' : 'jpg'}`
Toast.showLoading({ title: '上传中...', duration: 30e3 }) Toast.showLoading({ title: '上传中...', duration: 30e3 })
const file = { // 上传到云端
name: result.fileName, const fileBlob = await fetch(uri).then((r) => r.blob())
type: result.mimeType || 'image/jpeg', const mimeType = fileBlob.type || (isVideo ? 'video/mp4' : 'image/jpeg')
uri: Platform.OS === 'android' ? result.uri : result.uri.replace('file://', ''),
}
const formData = new FormData()
formData.append('file', file as any)
const { url, error } = await uploadFile(file as any) const file = new File([fileBlob], fileName, { type: mimeType })
const { url, error } = await uploadFile(file)
console.log({ error })
Toast.hideLoading() Toast.hideLoading()
if (error || !url) { if (error || !url) {
@ -519,11 +531,11 @@ const Sync = () => {
const newItem = { const newItem = {
id: `local-${Date.now()}`, id: `local-${Date.now()}`,
// type: isVideo ? 'video' : 'image', type: isVideo ? 'video' : 'image',
imageUrl: url, imageUrl: url,
url, url,
originalUrl: url, originalUrl: url,
// ...(typeof asset === 'object' ? asset : {}), ...(typeof asset === 'object' ? asset : {}),
} }
setSelectedItem(newItem) setSelectedItem(newItem)
@ -675,7 +687,7 @@ const Sync = () => {
viewableIds.current = new Set(viewableItems.map((v: any) => v.item.id)) viewableIds.current = new Set(viewableItems.map((v: any) => v.item.id))
}, []) }, [])
const renderHeader = useMemo( const renderHeaderFlatList = useMemo(
() => ( () => (
<Block className="z-10"> <Block className="z-10">
<HeaderBanner connectedDevice={connectedDevice} onPick={handlePick} /> <HeaderBanner connectedDevice={connectedDevice} onPick={handlePick} />
@ -721,7 +733,7 @@ const Sync = () => {
getItemType={() => 'row'} getItemType={() => 'row'}
removeClippedSubviews removeClippedSubviews
keyExtractor={(item: any) => item?.id} keyExtractor={(item: any) => item?.id}
ListHeaderComponent={renderHeader} ListHeaderComponent={renderHeaderFlatList}
renderItem={renderGridItem} renderItem={renderGridItem}
ItemSeparatorComponent={() => <Block style={{ height: 6 }} />} ItemSeparatorComponent={() => <Block style={{ height: 6 }} />}
contentContainerStyle={{ paddingHorizontal: 12, paddingBottom: 200 }} contentContainerStyle={{ paddingHorizontal: 12, paddingBottom: 200 }}

View File

@ -10,7 +10,7 @@ import { Stack, useRouter } from 'expo-router'
import Alipay from 'expo-native-alipay' import Alipay from 'expo-native-alipay'
import ExpoWeChat from 'expo-wechat' import ExpoWeChat from 'expo-wechat'
import { alipay } from '@/lib/auth' import { alipay } from '@/lib/auth'
import { ANDROID_ID, IOS_UNIVERSAL_LINK, SCHEME } from '@/app.constants' import { ANDROID_ID, IOS_UNIVERSAL_LINK } from '@/app.constants'
import { useUserBalance } from '@/hooks/core' import { useUserBalance } from '@/hooks/core'
const { width: SCREEN_WIDTH } = Dimensions.get('window') const { width: SCREEN_WIDTH } = Dimensions.get('window')
@ -44,7 +44,7 @@ export default function ChargePage() {
console.log('initpay ---------', Alipay) console.log('initpay ---------', Alipay)
Alipay.setAlipayScheme(SCHEME) Alipay.setAlipayScheme(ANDROID_ID)
// ⚠️ 目前不可用,设置支付宝沙箱环境,仅 Android 支持 // ⚠️ 目前不可用,设置支付宝沙箱环境,仅 Android 支持
Alipay.setAlipaySandbox(true) Alipay.setAlipaySandbox(true)