163 lines
4.2 KiB
TypeScript
163 lines
4.2 KiB
TypeScript
import React, { memo, useCallback, useState } from 'react'
|
|
import { View, StyleSheet, Text, Pressable, ActivityIndicator } from 'react-native'
|
|
import { Ionicons } from '@expo/vector-icons'
|
|
|
|
export interface VideoSocialButtonProps {
|
|
templateId: string
|
|
liked?: boolean
|
|
favorited?: boolean
|
|
likeCount?: number
|
|
favoriteCount?: number
|
|
loading?: boolean
|
|
onLike?: () => Promise<void> | void
|
|
onUnlike?: () => Promise<void> | void
|
|
onFavorite?: () => Promise<void> | void
|
|
onUnfavorite?: () => Promise<void> | void
|
|
testID?: string
|
|
}
|
|
|
|
const VideoSocialButtonComponent: React.FC<VideoSocialButtonProps> = ({
|
|
templateId,
|
|
liked = false,
|
|
favorited = false,
|
|
likeCount,
|
|
favoriteCount,
|
|
loading = false,
|
|
onLike,
|
|
onUnlike,
|
|
onFavorite,
|
|
onUnfavorite,
|
|
testID,
|
|
}) => {
|
|
// 本地 loading 状态,防止重复点击
|
|
const [localLoading, setLocalLoading] = useState(false)
|
|
|
|
// 处理点赞按钮点击
|
|
const handleLikePress = useCallback(async () => {
|
|
if (loading || localLoading) return
|
|
|
|
setLocalLoading(true)
|
|
try {
|
|
if (liked) {
|
|
await onUnlike?.()
|
|
} else {
|
|
await onLike?.()
|
|
}
|
|
} finally {
|
|
setLocalLoading(false)
|
|
}
|
|
}, [loading, localLoading, liked, onLike, onUnlike])
|
|
|
|
// 处理收藏按钮点击
|
|
const handleFavoritePress = useCallback(async () => {
|
|
if (loading || localLoading) return
|
|
|
|
setLocalLoading(true)
|
|
try {
|
|
if (favorited) {
|
|
await onUnfavorite?.()
|
|
} else {
|
|
await onFavorite?.()
|
|
}
|
|
} finally {
|
|
setLocalLoading(false)
|
|
}
|
|
}, [loading, localLoading, favorited, onFavorite, onUnfavorite])
|
|
|
|
// 格式化数量显示
|
|
const formatCount = (count?: number): string => {
|
|
if (count === undefined || count === null) return '0'
|
|
if (count >= 10000) {
|
|
return `${(count / 10000).toFixed(1)}w`
|
|
}
|
|
if (count >= 1000) {
|
|
return `${(count / 1000).toFixed(1)}k`
|
|
}
|
|
return count.toString()
|
|
}
|
|
|
|
const isLoading = loading || localLoading
|
|
|
|
return (
|
|
<View style={styles.container} testID={testID}>
|
|
{/* 点赞按钮 */}
|
|
<Pressable
|
|
style={styles.buttonWrapper}
|
|
onPress={handleLikePress}
|
|
disabled={isLoading}
|
|
testID={testID ? `${testID}-like-button` : undefined}
|
|
>
|
|
<View style={[styles.iconContainer, liked && styles.iconContainerActive]}>
|
|
{isLoading && liked ? (
|
|
<ActivityIndicator size="small" color="#FF3B30" />
|
|
) : (
|
|
<Ionicons
|
|
name={liked ? 'heart' : 'heart-outline'}
|
|
size={28}
|
|
color={liked ? '#FF3B30' : '#FFFFFF'}
|
|
/>
|
|
)}
|
|
</View>
|
|
<Text style={styles.count}>{formatCount(likeCount)}</Text>
|
|
</Pressable>
|
|
|
|
{/* 收藏按钮 */}
|
|
<Pressable
|
|
style={styles.buttonWrapper}
|
|
onPress={handleFavoritePress}
|
|
disabled={isLoading}
|
|
testID={testID ? `${testID}-favorite-button` : undefined}
|
|
>
|
|
<View style={[styles.iconContainer, favorited && styles.iconContainerActive]}>
|
|
{isLoading && favorited ? (
|
|
<ActivityIndicator size="small" color="#FFD700" />
|
|
) : (
|
|
<Ionicons
|
|
name={favorited ? 'star' : 'star-outline'}
|
|
size={28}
|
|
color={favorited ? '#FFD700' : '#FFFFFF'}
|
|
/>
|
|
)}
|
|
</View>
|
|
<Text style={styles.count}>{formatCount(favoriteCount)}</Text>
|
|
</Pressable>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export const VideoSocialButton = memo(VideoSocialButtonComponent)
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
position: 'absolute',
|
|
right: 12,
|
|
bottom: 180,
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
gap: 20,
|
|
},
|
|
buttonWrapper: {
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
},
|
|
iconContainer: {
|
|
width: 52,
|
|
height: 52,
|
|
borderRadius: 26,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
iconContainerActive: {
|
|
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
|
},
|
|
count: {
|
|
color: '#FFFFFF',
|
|
fontSize: 12,
|
|
fontWeight: '600',
|
|
textShadowColor: 'rgba(0, 0, 0, 0.5)',
|
|
textShadowOffset: { width: 0, height: 1 },
|
|
textShadowRadius: 2,
|
|
},
|
|
})
|