feat: 更新 Img 和 Video 组件,优化视频处理逻辑,调整支付宝支付方案
This commit is contained in:
parent
93122d9955
commit
11f5907d4e
|
|
@ -52,7 +52,7 @@ const Img = forwardRef<ExpoImage, ImgProps>((props, ref) => {
|
|||
const imgProps = {
|
||||
style: [style, imageStyle],
|
||||
ref,
|
||||
placeholder: blurhash,
|
||||
// placeholder: blurhash,
|
||||
source: imageSource,
|
||||
cachePolicy: 'memory-disk' as const,
|
||||
errorSource,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Image } from 'expo-image'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { memo, useEffect, useMemo, useState } from 'react'
|
||||
import { ViewStyle } from 'react-native'
|
||||
import Video from 'react-native-video'
|
||||
|
||||
|
|
@ -7,19 +7,58 @@ type Props = {
|
|||
url?: string
|
||||
poster?: string
|
||||
style?: ViewStyle
|
||||
width?: number
|
||||
} & React.ComponentProps<typeof Video>
|
||||
|
||||
const VideoBox = ({ url, poster, style, ...videoProps }: Props) => {
|
||||
const [paused, setPaused] = useState(true)
|
||||
const VideoBox = ({ url, poster, width = 120, style, ...videoProps }: Props) => {
|
||||
const [urlFinal, setUrlFinal] = useState('')
|
||||
|
||||
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(() => {
|
||||
console.log('url--------', url);
|
||||
if (!url) return
|
||||
setRedirectUrl(url!)
|
||||
// const finalUrl = createUrl(url)
|
||||
// console.log('finalUrl-----------', finalUrl)
|
||||
|
||||
setPaused(!Boolean(url))
|
||||
// setUrlFinal(finalUrl)
|
||||
return
|
||||
}, [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`
|
||||
// console.log('urlFinal--------- ', urlFinal, url)
|
||||
|
||||
if (!url) return null
|
||||
return (
|
||||
// 当 url 变化时通过 key 强制重载,确保自动播放生效
|
||||
// <Video
|
||||
|
|
@ -36,7 +75,7 @@ const VideoBox = ({ url, poster, style, ...videoProps }: Props) => {
|
|||
// style={style as any}
|
||||
// {...videoProps}
|
||||
// />
|
||||
<Image source={{ uri: url ? createUrl(url) : '' }} style={style as any} />
|
||||
<Image key={urlFinal} cachePolicy="memory-disk" source={{ uri: urlFinal }} style={style as any} />
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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 { Dimensions, Platform, 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'
|
||||
|
|
@ -17,7 +17,7 @@ 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'
|
||||
import * as FileSystem from 'expo-file-system'
|
||||
|
||||
// ============ 常量定义 ============
|
||||
const BACKGROUND_VIDEOS = [
|
||||
|
|
@ -115,11 +115,6 @@ const GridItem = memo(
|
|||
style={{ transform: [{ skewX: '-6deg' }], 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" />
|
||||
</Block>
|
||||
{isSelected && <Block className="absolute inset-[0px] border-[3px] border-accent" />}
|
||||
|
|
@ -293,23 +288,17 @@ const TopCircleSection = memo(
|
|||
)
|
||||
|
||||
const GalleryRenderer = memo(({ selectedItem }: { selectedItem: any }) => {
|
||||
const uri = selectedItem?.url || selectedItem.imageUrl
|
||||
console.log('GalleryRenderer------------', uri)
|
||||
const url = selectedItem?.url || selectedItem.imageUrl
|
||||
console.log('GalleryRenderer------------', url)
|
||||
|
||||
if (!uri) return null
|
||||
if (!url) return null
|
||||
|
||||
const Width = 256
|
||||
if (isVideoUrl(uri)) {
|
||||
return (
|
||||
<View style={{ width: Width, height: Width, borderRadius: Width, overflow: 'hidden' }} className="relative z-[10] border-[4px] border-black">
|
||||
<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" />
|
||||
return (
|
||||
<View style={{ width: Width, height: Width, borderRadius: Width, overflow: 'hidden' }} className="relative z-[10] border-[4px] border-black">
|
||||
<VideoBox width={Width} url={url} style={{ width: Width, height: Width }} />
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
const ManagerView = memo(
|
||||
|
|
@ -346,26 +335,11 @@ const ManagerView = memo(
|
|||
)
|
||||
|
||||
const BackgroundBanner = memo(({ selectedItem }: { selectedItem: any }) => {
|
||||
const uri = selectedItem?.url || selectedItem.imageUrl
|
||||
// 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>
|
||||
// )
|
||||
// }
|
||||
const url = selectedItem?.url || selectedItem.imageUrl
|
||||
console.log('BackgroundBanner------------', url)
|
||||
return (
|
||||
<Block className="absolute inset-0 bottom-0 left-0 right-0 top-0 z-[10] overflow-hidden">
|
||||
{/* <VideoBox url={BACKGROUND_VIDEOS[0]} style={{ width: screenWidth, height: screenHeight }} /> */}
|
||||
<Img src={bgGif} style={{ width: screenWidth, height: screenHeight }} />
|
||||
<VideoBox width={512} url={url} style={{ width: screenWidth, height: screenHeight }} />
|
||||
</Block>
|
||||
)
|
||||
})
|
||||
|
|
@ -399,7 +373,7 @@ const Sync = () => {
|
|||
// 加载生成记录
|
||||
useEffect(() => {
|
||||
if (user?.id) {
|
||||
loadGenerations({ userId: user.id })
|
||||
loadGenerations()
|
||||
}
|
||||
}, [user?.id, loadGenerations])
|
||||
|
||||
|
|
@ -421,6 +395,20 @@ const Sync = () => {
|
|||
}))
|
||||
}, [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)))
|
||||
|
||||
// 动画效果
|
||||
|
|
@ -505,21 +493,22 @@ const Sync = () => {
|
|||
|
||||
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
|
||||
const result = assetList[0] as any
|
||||
|
||||
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 = {
|
||||
name: result.fileName,
|
||||
type: result.mimeType || 'image/jpeg',
|
||||
uri: Platform.OS === 'android' ? result.uri : result.uri.replace('file://', ''),
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('file', file as any)
|
||||
|
||||
const file = new File([fileBlob], fileName, { type: mimeType })
|
||||
|
||||
const { url, error } = await uploadFile(file)
|
||||
const { url, error } = await uploadFile(file as any)
|
||||
|
||||
Toast.hideLoading()
|
||||
|
||||
|
|
@ -530,11 +519,11 @@ const Sync = () => {
|
|||
|
||||
const newItem = {
|
||||
id: `local-${Date.now()}`,
|
||||
type: isVideo ? 'video' : 'image',
|
||||
// type: isVideo ? 'video' : 'image',
|
||||
imageUrl: url,
|
||||
url,
|
||||
originalUrl: url,
|
||||
...(typeof asset === 'object' ? asset : {}),
|
||||
// ...(typeof asset === 'object' ? asset : {}),
|
||||
}
|
||||
|
||||
setSelectedItem(newItem)
|
||||
|
|
@ -629,7 +618,7 @@ const Sync = () => {
|
|||
|
||||
// 刷新列表
|
||||
if (user?.id) {
|
||||
loadGenerations({ userId: user.id })
|
||||
loadGenerations()
|
||||
}
|
||||
}, [selectedItem, runTemplate, user?.id, loadGenerations])
|
||||
|
||||
|
|
@ -666,7 +655,7 @@ const Sync = () => {
|
|||
|
||||
// 刷新列表
|
||||
if (user?.id) {
|
||||
await loadGenerations({ userId: user.id })
|
||||
await loadGenerations()
|
||||
}
|
||||
|
||||
setSelectedIds(new Set())
|
||||
|
|
@ -686,7 +675,7 @@ const Sync = () => {
|
|||
viewableIds.current = new Set(viewableItems.map((v: any) => v.item.id))
|
||||
}, [])
|
||||
|
||||
const renderHeaderFlatList = useMemo(
|
||||
const renderHeader = useMemo(
|
||||
() => (
|
||||
<Block className="z-10">
|
||||
<HeaderBanner connectedDevice={connectedDevice} onPick={handlePick} />
|
||||
|
|
@ -732,7 +721,7 @@ const Sync = () => {
|
|||
getItemType={() => 'row'}
|
||||
removeClippedSubviews
|
||||
keyExtractor={(item: any) => item?.id}
|
||||
ListHeaderComponent={renderHeaderFlatList}
|
||||
ListHeaderComponent={renderHeader}
|
||||
renderItem={renderGridItem}
|
||||
ItemSeparatorComponent={() => <Block style={{ height: 6 }} />}
|
||||
contentContainerStyle={{ paddingHorizontal: 12, paddingBottom: 200 }}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Stack, useRouter } from 'expo-router'
|
|||
import Alipay from 'expo-native-alipay'
|
||||
import ExpoWeChat from 'expo-wechat'
|
||||
import { alipay } from '@/lib/auth'
|
||||
import { ANDROID_ID, IOS_UNIVERSAL_LINK } from '@/app.constants'
|
||||
import { ANDROID_ID, IOS_UNIVERSAL_LINK, SCHEME } from '@/app.constants'
|
||||
import { useUserBalance } from '@/hooks/core'
|
||||
|
||||
const { width: SCREEN_WIDTH } = Dimensions.get('window')
|
||||
|
|
@ -44,7 +44,7 @@ export default function ChargePage() {
|
|||
|
||||
console.log('initpay ---------', Alipay)
|
||||
|
||||
Alipay.setAlipayScheme(ANDROID_ID)
|
||||
Alipay.setAlipayScheme(SCHEME)
|
||||
// ⚠️ 目前不可用,设置支付宝沙箱环境,仅 Android 支持
|
||||
Alipay.setAlipaySandbox(true)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue