276 lines
7.9 KiB
TypeScript
276 lines
7.9 KiB
TypeScript
import { useState, useRef } from 'react'
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
Dimensions,
|
|
FlatList,
|
|
Pressable,
|
|
StatusBar as RNStatusBar,
|
|
} from 'react-native'
|
|
import { StatusBar } from 'expo-status-bar'
|
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
import { Image, ImageLoadEventData } from 'expo-image'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
import { SameStyleIcon, VideoIcon, WhiteStarIcon } from '@/components/icon'
|
|
import { useRouter } from 'expo-router'
|
|
|
|
const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
|
|
const TAB_BAR_HEIGHT = 83 // 底部导航栏高度
|
|
|
|
// 视频数据
|
|
const videoData = [
|
|
{
|
|
id: 1,
|
|
videoUrl: require('@/assets/images/android-icon-background.png'),
|
|
thumbnailUrl: require('@/assets/images/android-icon-background.png'),
|
|
title: '雅琳圣诞写真',
|
|
duration: '00:03',
|
|
},
|
|
{
|
|
id: 2,
|
|
videoUrl: require('@/assets/images/android-icon-background.png'),
|
|
thumbnailUrl: require('@/assets/images/android-icon-background.png'),
|
|
title: '宠物写真',
|
|
duration: '00:05',
|
|
},
|
|
{
|
|
id: 3,
|
|
videoUrl: require('@/assets/images/android-icon-background.png'),
|
|
thumbnailUrl: require('@/assets/images/android-icon-background.png'),
|
|
title: '我和小猫的人生合照',
|
|
duration: '00:04',
|
|
},
|
|
]
|
|
|
|
interface VideoItemProps {
|
|
item: typeof videoData[0]
|
|
index: number
|
|
videoHeight: number
|
|
}
|
|
|
|
function VideoItem({ item, videoHeight }: VideoItemProps) {
|
|
const { t } = useTranslation()
|
|
const router = useRouter()
|
|
const [isPlaying, setIsPlaying] = useState(false)
|
|
const [imageSize, setImageSize] = useState<{ width: number; height: number } | null>(null)
|
|
|
|
const handleImageLoad = (event: ImageLoadEventData) => {
|
|
const { source } = event
|
|
if (source?.width && source?.height) {
|
|
setImageSize({ width: source.width, height: source.height })
|
|
}
|
|
}
|
|
|
|
// 根据图片比例计算显示尺寸
|
|
const getImageStyle = () => {
|
|
if (!imageSize) {
|
|
return {
|
|
width: screenWidth,
|
|
height: videoHeight,
|
|
}
|
|
}
|
|
|
|
const imageAspectRatio = imageSize.width / imageSize.height
|
|
const screenAspectRatio = screenWidth / videoHeight
|
|
|
|
let displayWidth = screenWidth
|
|
let displayHeight = videoHeight
|
|
|
|
if (imageAspectRatio > screenAspectRatio) {
|
|
// 图片更宽,以宽度为准
|
|
displayHeight = screenWidth / imageAspectRatio
|
|
} else {
|
|
// 图片更高,以高度为准
|
|
displayWidth = videoHeight * imageAspectRatio
|
|
}
|
|
|
|
return {
|
|
width: displayWidth,
|
|
height: displayHeight,
|
|
}
|
|
}
|
|
|
|
return (
|
|
<View style={[styles.videoContainer, { height: videoHeight }]}>
|
|
{/* 主视频区域 */}
|
|
<View style={styles.videoWrapper}>
|
|
<Image
|
|
source={item.videoUrl}
|
|
style={[getImageStyle()]}
|
|
contentFit="contain"
|
|
onLoad={handleImageLoad}
|
|
/>
|
|
{/* 播放按钮 */}
|
|
{!isPlaying && (
|
|
<Pressable
|
|
style={styles.playButton}
|
|
onPress={() => setIsPlaying(true)}
|
|
>
|
|
<View style={styles.playIcon}>
|
|
<View style={styles.playTriangle} />
|
|
</View>
|
|
</Pressable>
|
|
)}
|
|
|
|
{/* 左下角原图缩略图 */}
|
|
<View style={styles.thumbnailContainer}>
|
|
<Image
|
|
source={item.thumbnailUrl}
|
|
style={styles.thumbnail}
|
|
contentFit="cover"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 左下角按钮 */}
|
|
<Pressable style={styles.actionButtonLeft}>
|
|
<WhiteStarIcon />
|
|
<Text style={styles.actionButtonTextTitle}>{item.title}</Text>
|
|
</Pressable>
|
|
|
|
{/* 右下角按钮 */}
|
|
<Pressable
|
|
style={styles.actionButtonRight}
|
|
onPress={() => {
|
|
router.push({
|
|
pathname: '/generateVideo' as any,
|
|
params: {
|
|
template: JSON.stringify(item),
|
|
},
|
|
})
|
|
}}
|
|
>
|
|
<SameStyleIcon />
|
|
<Text style={styles.actionButtonText}>{t('video.makeSame')}</Text>
|
|
</Pressable>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export default function VideoScreen() {
|
|
const flatListRef = useRef<FlatList>(null)
|
|
const insets = useSafeAreaInsets()
|
|
const videoHeight = screenHeight - TAB_BAR_HEIGHT
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={[]}>
|
|
<StatusBar style="light" />
|
|
<RNStatusBar barStyle="light-content" />
|
|
<FlatList
|
|
ref={flatListRef}
|
|
data={videoData}
|
|
renderItem={({ item, index }) => (
|
|
<VideoItem item={item} index={index} videoHeight={videoHeight} />
|
|
)}
|
|
keyExtractor={(item) => item.id.toString()}
|
|
pagingEnabled
|
|
showsVerticalScrollIndicator={false}
|
|
snapToInterval={videoHeight}
|
|
snapToAlignment="start"
|
|
decelerationRate="fast"
|
|
getItemLayout={(_, index) => ({
|
|
length: videoHeight,
|
|
offset: videoHeight * index,
|
|
index,
|
|
})}
|
|
/>
|
|
</SafeAreaView>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
videoContainer: {
|
|
width: screenWidth,
|
|
backgroundColor: '#090A0B',
|
|
position: 'relative',
|
|
},
|
|
videoWrapper: {
|
|
flex: 1,
|
|
position: 'relative',
|
|
backgroundColor: '#090A0B',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
playButton: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
|
},
|
|
playIcon: {
|
|
width: 56,
|
|
height: 56,
|
|
borderRadius: 28,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.85)',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
playTriangle: {
|
|
width: 0,
|
|
height: 0,
|
|
borderLeftWidth: 18,
|
|
borderTopWidth: 11,
|
|
borderBottomWidth: 11,
|
|
borderLeftColor: '#000000',
|
|
borderTopColor: 'transparent',
|
|
borderBottomColor: 'transparent',
|
|
marginLeft: 3,
|
|
},
|
|
thumbnailContainer: {
|
|
position: 'absolute',
|
|
left: 12,
|
|
bottom: 80,
|
|
width: 56,
|
|
height: 56,
|
|
borderRadius: 8,
|
|
overflow: 'hidden',
|
|
borderWidth: 2,
|
|
borderColor: '#FFFFFF',
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
thumbnail: {
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
actionButtonLeft: {
|
|
position: 'absolute',
|
|
left: 12,
|
|
bottom: 16,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
paddingHorizontal: 6,
|
|
paddingVertical: 8,
|
|
backgroundColor: '#191B1F',
|
|
borderRadius: 8,
|
|
borderWidth: 1,
|
|
borderColor: '#2F3134',
|
|
},
|
|
actionButtonRight: {
|
|
position: 'absolute',
|
|
right: 13,
|
|
bottom: 13,
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
},
|
|
actionButtonTextTitle: {
|
|
color: '#F5F5F5',
|
|
fontSize: 11,
|
|
},
|
|
actionButtonText: {
|
|
color: '#CCCCCC',
|
|
fontSize: 10,
|
|
fontWeight: '500',
|
|
},
|
|
})
|