192 lines
5.3 KiB
TypeScript
192 lines
5.3 KiB
TypeScript
import { useState } from 'react'
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
Dimensions,
|
|
Pressable,
|
|
ActivityIndicator,
|
|
} from 'react-native'
|
|
import { Image } from 'expo-image'
|
|
import { useRouter } from 'expo-router'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
import { WhiteStarIcon } from '@/components/icon'
|
|
import type { TemplateDetail } from '@/hooks'
|
|
|
|
const { width: screenWidth } = Dimensions.get('window')
|
|
|
|
interface TemplateSearchResultItem {
|
|
id: string
|
|
title: string
|
|
image: string | { uri: string }
|
|
previewUrl?: string
|
|
coverImageUrl?: string
|
|
aspectRatio?: number
|
|
height?: number
|
|
}
|
|
|
|
interface SearchResultsGridProps {
|
|
results: TemplateSearchResultItem[]
|
|
loading?: boolean
|
|
onEndReached?: () => void
|
|
ListFooterComponent?: React.ReactElement | null
|
|
}
|
|
|
|
// 计算卡片高度的辅助函数
|
|
const calculateCardHeight = (width: number, aspectRatio?: number): number => {
|
|
if (aspectRatio) {
|
|
return width / aspectRatio
|
|
}
|
|
// 默认宽高比
|
|
return width * 1.2
|
|
}
|
|
|
|
export default function SearchResultsGrid({ results, loading, onEndReached, ListFooterComponent }: SearchResultsGridProps) {
|
|
const { t } = useTranslation()
|
|
const router = useRouter()
|
|
const [gridWidth, setGridWidth] = useState(screenWidth)
|
|
|
|
const horizontalPadding = 8 * 2
|
|
const cardGap = 5
|
|
const cardWidth = (gridWidth - horizontalPadding - cardGap) / 2
|
|
|
|
const handleCardPress = (item: TemplateSearchResultItem) => {
|
|
router.push({
|
|
pathname: '/templateDetail' as any,
|
|
params: { id: item.id.toString() },
|
|
})
|
|
}
|
|
|
|
const handleScroll = (event: any) => {
|
|
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent
|
|
const paddingToBottom = 20
|
|
if (layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom) {
|
|
onEndReached?.()
|
|
}
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color="#FF6699" />
|
|
</View>
|
|
)
|
|
}
|
|
|
|
if (results.length === 0) {
|
|
return (
|
|
<View style={styles.emptyContainer}>
|
|
<Text style={styles.emptyText}>{t('searchResults.noResults')}</Text>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<ScrollView
|
|
style={styles.scrollView}
|
|
showsVerticalScrollIndicator={false}
|
|
onScroll={handleScroll}
|
|
scrollEventThrottle={400}
|
|
>
|
|
<View
|
|
style={styles.gridContainer}
|
|
onLayout={(event) => {
|
|
const { width } = event.nativeEvent.layout
|
|
setGridWidth(width)
|
|
}}
|
|
>
|
|
{results.map((item, index) => {
|
|
const height = item.height || calculateCardHeight(cardWidth, item.aspectRatio)
|
|
|
|
return (
|
|
<Pressable
|
|
key={item.id}
|
|
style={[
|
|
styles.card,
|
|
{ width: cardWidth },
|
|
index % 2 === 0 ? styles.cardLeft : styles.cardRight,
|
|
]}
|
|
onPress={() => handleCardPress(item)}
|
|
>
|
|
<View style={[styles.cardImageContainer, { height }]}>
|
|
<Image
|
|
source={item.image}
|
|
style={styles.cardImage}
|
|
contentFit="cover"
|
|
/>
|
|
<Text style={styles.cardTitle} numberOfLines={1}>
|
|
{item.title}
|
|
</Text>
|
|
</View>
|
|
</Pressable>
|
|
)
|
|
})}
|
|
</View>
|
|
{ListFooterComponent}
|
|
</ScrollView>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
loadingContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
paddingVertical: 40,
|
|
},
|
|
scrollView: {
|
|
flex: 1,
|
|
},
|
|
gridContainer: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
paddingHorizontal: 8,
|
|
justifyContent: 'space-between',
|
|
},
|
|
card: {
|
|
backgroundColor: '#16181B',
|
|
borderBottomLeftRadius: 12,
|
|
borderBottomRightRadius: 12,
|
|
marginBottom: 12,
|
|
overflow: 'hidden',
|
|
},
|
|
cardLeft: {
|
|
marginRight: 0,
|
|
},
|
|
cardRight: {
|
|
marginLeft: 0,
|
|
},
|
|
cardImageContainer: {
|
|
width: '100%',
|
|
borderRadius: 12,
|
|
overflow: 'hidden',
|
|
marginBottom: 8,
|
|
position: 'relative',
|
|
},
|
|
cardImage: {
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
cardTitle: {
|
|
position: 'absolute',
|
|
bottom: 8,
|
|
left: 8,
|
|
color: '#FFFFFF',
|
|
fontSize: 12,
|
|
fontWeight: '500',
|
|
},
|
|
emptyContainer: {
|
|
flex: 1,
|
|
backgroundColor: '#090A0B',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
paddingVertical: 60,
|
|
},
|
|
emptyText: {
|
|
color: '#8A8A8A',
|
|
fontSize: 14,
|
|
},
|
|
})
|