This commit is contained in:
imeepos 2026-01-16 17:10:22 +08:00
parent dcdab410c6
commit 76ce6fb1db
5 changed files with 103 additions and 82 deletions

View File

@ -1 +0,0 @@
{ "recommendations": ["expo.vscode-expo-tools"] }

View File

@ -1,7 +0,0 @@
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit",
"source.sortMembers": "explicit"
}
}

View File

@ -1,10 +1,12 @@
import { Image } from 'expo-image'
import { VideoView, useVideoPlayer } from 'expo-video'
import { LinearGradient } from 'expo-linear-gradient'
import { useRouter, useLocalSearchParams } from 'expo-router'
import { StatusBar } from 'expo-status-bar'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FlashList } from '@shopify/flash-list';
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { VideoView, useVideoPlayer } from 'expo-video';
import { memo as ReactMemo, useEffect, useRef, useState } from 'react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Dimensions,
Platform,
@ -14,16 +16,70 @@ import {
StyleSheet,
Text,
View
} from 'react-native'
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
} from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { DownArrowIcon, PointsIcon, SearchIcon, WhiteStarIcon } from '@/components/icon'
import { HomeSkeleton } from '@/components/skeleton/HomeSkeleton'
import { useActivates } from '@/hooks/use-activates'
import { useCategories } from '@/hooks/use-categories'
import { DownArrowIcon, PointsIcon, SearchIcon, WhiteStarIcon } from '@/components/icon';
import { HomeSkeleton } from '@/components/skeleton/HomeSkeleton';
import { useActivates } from '@/hooks/use-activates';
import { useCategories } from '@/hooks/use-categories';
const { width: screenWidth } = Dimensions.get('window')
// 卡片组件 - 使用 useCallback 缓存以优化 FlashList 性能
const Card = ReactMemo(({ card, cardWidth, t, onPress }: {
card: any
cardWidth: number
t: any
onPress: (id: string) => void
}) => {
return (
<Pressable
style={[styles.card, { width: cardWidth }]}
onPress={() => onPress(card.id)}
>
<View
style={[
styles.cardImageContainer,
{ height: card.height || cardWidth * 1.2 },
]}
>
{card.isVideo ? (
<VideoPreview source={card.image.uri} style={styles.cardImage} />
) : (
<Image
source={card.image}
style={styles.cardImage}
contentFit="cover"
/>
)}
<LinearGradient
colors={['rgba(17, 17, 17, 0)', 'rgba(17, 17, 17, 0.9)']}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
style={styles.cardImageGradient}
/>
{card.isHot ? (
<View style={styles.hotBadge}>
<Text style={styles.hotEmoji}>🔥</Text>
<Text style={styles.hotText}>{t('home.hotTemplate')}</Text>
</View>
) : (
<View style={styles.hotBadge}>
<WhiteStarIcon />
<Text style={styles.hotText}>
{card.users}{t('home.peopleUsed')}
</Text>
</View>
)}
<Text style={styles.cardTitle} numberOfLines={1}>
{card.title}
</Text>
</View>
</Pressable>
)
})
// 视频预览组件
function VideoPreview({ source, style }: { source: string; style: any }) {
const player = useVideoPlayer(source, (player) => {
@ -332,7 +388,7 @@ export default function HomeScreen() {
</View>
)}
{/* 内容网格 */}
{/* 内容网格 - 使用 FlashList 优化性能 */}
{!showLoading && !showEmptyState && !showEmptyTemplates && (
<View
style={styles.gridContainer}
@ -341,61 +397,28 @@ export default function HomeScreen() {
setGridWidth(width)
}}
>
{displayCardData.map((card, index) => (
<Pressable
key={card.id}
style={[
styles.card,
{ width: cardWidth },
index % 2 === 0 ? styles.cardLeft : styles.cardRight,
]}
onPress={() => {
router.push({
pathname: '/templateDetail' as any,
params: { id: card.id.toString() },
})
}}
>
<View
style={[
styles.cardImageContainer,
{ height: card.height || cardWidth * 1.2 },
]}
>
{card.isVideo ? (
<VideoPreview source={card.image.uri} style={styles.cardImage} />
) : (
<Image
source={card.image}
style={styles.cardImage}
contentFit="cover"
/>
)}
<LinearGradient
colors={['rgba(17, 17, 17, 0)', 'rgba(17, 17, 17, 0.9)']}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
style={styles.cardImageGradient}
/>
{card.isHot ? (
<View style={styles.hotBadge}>
<Text style={styles.hotEmoji}>🔥</Text>
<Text style={styles.hotText}>{t('home.hotTemplate')}</Text>
</View>
) : (
<View style={styles.hotBadge}>
<WhiteStarIcon />
<Text style={styles.hotText}>
{card.users}{t('home.peopleUsed')}
</Text>
</View>
)}
<Text style={styles.cardTitle} numberOfLines={1}>
{card.title}
</Text>
</View>
</Pressable>
))}
<FlashList
data={displayCardData}
renderItem={({ item, index }) => (
<Card
card={item}
cardWidth={cardWidth}
t={t}
onPress={(id) => {
router.push({
pathname: '/templateDetail' as any,
params: { id: id.toString() },
})
}}
/>
)}
keyExtractor={(item) => item.id}
numColumns={2}
estimatedItemSize={cardWidth * 1.2 + 60}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.flashListContent}
scrollEventThrottle={Platform.OS === 'ios' ? 16 : 50}
/>
</View>
)}
</ScrollView>
@ -622,12 +645,10 @@ const styles = StyleSheet.create({
},
card: {
marginBottom: 12,
paddingHorizontal: 5,
},
cardLeft: {
marginRight: 0,
},
cardRight: {
marginLeft: 0,
flashListContent: {
gap: 10,
},
cardImageContainer: {
width: '100%',

View File

@ -15,6 +15,7 @@
"@react-navigation/native": "^7.1.8",
"@repo/core": "1.0.2",
"@repo/sdk": "1.0.7",
"@shopify/flash-list": "^1.7.3",
"@stripe/react-stripe-js": "^5.4.1",
"@stripe/stripe-js": "^8.5.3",
"@stripe/stripe-react-native": "^0.57.0",
@ -631,6 +632,8 @@
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
"@shopify/flash-list": ["@shopify/flash-list@1.8.3", "", { "dependencies": { "recyclerlistview": "4.2.3", "tslib": "2.8.1" }, "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-vXuj6JyuMjONVOXjEhWFeaONPuWN/53Cl2LeyeM8TZ0JzUcNU+BE6iyga1/yyJeDf0K7YPgAE/PcUX2+DM1LiA=="],
"@simplewebauthn/browser": ["@simplewebauthn/browser@13.2.2", "", {}, "sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA=="],
"@simplewebauthn/server": ["@simplewebauthn/server@13.2.2", "", { "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", "@peculiar/asn1-android": "^2.3.10", "@peculiar/asn1-ecc": "^2.3.8", "@peculiar/asn1-rsa": "^2.3.8", "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", "@peculiar/x509": "^1.13.0" } }, "sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA=="],
@ -1823,6 +1826,8 @@
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"recyclerlistview": ["recyclerlistview@4.2.3", "", { "dependencies": { "lodash.debounce": "4.0.8", "prop-types": "15.8.1", "ts-object-utils": "0.0.5" }, "peerDependencies": { "react": ">= 15.2.1", "react-native": ">= 0.30.0" } }, "sha512-STR/wj/FyT8EMsBzzhZ1l2goYirMkIgfV3gYEPxI3Kf3lOnu6f7Dryhyw7/IkQrgX5xtTcDrZMqytvteH9rL3g=="],
"reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
@ -2035,6 +2040,8 @@
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
"ts-object-utils": ["ts-object-utils@0.0.5", "", {}, "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA=="],
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],

View File

@ -26,6 +26,7 @@
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
"@shopify/flash-list": "^1.7.3",
"@stripe/react-stripe-js": "^5.4.1",
"@stripe/stripe-js": "^8.5.3",
"@stripe/stripe-react-native": "^0.57.0",