✨ feat: 应用界面重构 - 从模板中心到AI内容生成平台
- 重构首页:从模板列表展示改为AI功能展示界面 - 更新底部导航:将explore改为content,index改为home - 新增多个页面:积分、兑换、历史记录、结果展示、设置等 - 优化UI组件:新增通用按钮、分类标签、加载状态等组件 - 清理冗余文件:删除旧的认证文档和积分详情页面 - 更新主题配置:调整配色方案和视觉风格 - 修复导入路径:优化组件导入结构 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,349 +1,139 @@
|
||||
import { TemplateList } from '@/components/templates/template-list';
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import { ThemedView } from '@/components/themed-view';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
|
||||
import {
|
||||
CategoryTabs,
|
||||
CommunityGrid,
|
||||
FeatureCarousel,
|
||||
Header,
|
||||
PageLayout,
|
||||
SectionHeader,
|
||||
StatusBarSpacer,
|
||||
type CommunityItem,
|
||||
type FeatureItem,
|
||||
} from '@/components/bestai';
|
||||
import type { FeatureItem as FeatureItemType } from '@/components/bestai/feature-carousel';
|
||||
import { useAuth } from '@/hooks/use-auth';
|
||||
import { getTemplates } from '@/lib/api/templates';
|
||||
import { Template } from '@/lib/types/template';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Alert, Animated, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { LoginModal } from '@/components/auth/login-modal';
|
||||
import { useAuthGuard } from '@/hooks/use-auth-guard';
|
||||
|
||||
export default function HomeScreen() {
|
||||
const categories = [
|
||||
{ id: 'visual-effects', label: 'Visual Effects' },
|
||||
{ id: 'higgsfield-soul', label: 'Higgsfield Soul' },
|
||||
{ id: 'higgsfield-apps', label: 'Higgsfield Apps' },
|
||||
{ id: 'king', label: 'King' },
|
||||
];
|
||||
|
||||
const baseFeatureItems: FeatureItem[] = [
|
||||
{
|
||||
id: 'sketch-video',
|
||||
title: 'UNLIMITED SKETCH TO VIDEO',
|
||||
subtitle: 'Bring your imagination to life with Sora 2',
|
||||
image: 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=800&q=80',
|
||||
},
|
||||
{
|
||||
id: 'portrait-video',
|
||||
title: 'UNLIMITED PORTRAIT TO VIDEO',
|
||||
subtitle: 'Transform portraits into motion-driven stories',
|
||||
image: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=800&q=80',
|
||||
},
|
||||
{
|
||||
id: 'water-simulation',
|
||||
title: 'FLUID SIMULATION PACK',
|
||||
subtitle: 'Realistic water physics for cinematic scenes',
|
||||
image: 'https://images.unsplash.com/photo-1505744386214-51dba16a26fc?auto=format&fit=crop&w=800&q=80',
|
||||
},
|
||||
];
|
||||
|
||||
const baseCommunityItems: CommunityItem[] = [
|
||||
{
|
||||
id: 'community-1',
|
||||
chip: '64/10',
|
||||
title: 'OBJECTS AROUND',
|
||||
image: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=800&q=80',
|
||||
actionLabel: 'Generate',
|
||||
},
|
||||
{
|
||||
id: 'community-2',
|
||||
chip: '64/10',
|
||||
title: 'OBJECTS AROUND',
|
||||
image: 'https://images.unsplash.com/photo-1544723795-3fb6469f5b39?auto=format&fit=crop&w=800&q=80',
|
||||
actionLabel: 'Generate',
|
||||
},
|
||||
{
|
||||
id: 'community-3',
|
||||
chip: '64/10',
|
||||
title: 'OBJECTS AROUND',
|
||||
image: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=800&q=80',
|
||||
actionLabel: 'Generate',
|
||||
},
|
||||
{
|
||||
id: 'community-4',
|
||||
chip: '64/10',
|
||||
title: 'OBJECTS AROUND',
|
||||
image: 'https://images.unsplash.com/photo-1531259683007-016a7b628fc4?auto=format&fit=crop&w=800&q=80',
|
||||
actionLabel: 'Generate',
|
||||
},
|
||||
];
|
||||
|
||||
export default function ExploreScreen() {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const [templates, setTemplates] = useState<Template[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showLoginModal, setShowLoginModal] = useState(false);
|
||||
const pulseAnim = useRef(new Animated.Value(0)).current;
|
||||
const bounceAnim = useRef(new Animated.Value(0)).current;
|
||||
const { requireAuth } = useAuthGuard();
|
||||
const [activeCategory, setActiveCategory] = useState(categories[0]?.id ?? 'visual-effects');
|
||||
|
||||
const fetchTemplates = useCallback(async (page: number = 1, isRefreshing = false, isLoadMore = false) => {
|
||||
if (!isAuthenticated) return;
|
||||
const featureItems = useMemo(() => {
|
||||
return baseFeatureItems.map((item, index) => ({
|
||||
...item,
|
||||
id: `${activeCategory}-${index}`,
|
||||
}));
|
||||
}, [activeCategory]);
|
||||
|
||||
try {
|
||||
if (isRefreshing) {
|
||||
setRefreshing(true);
|
||||
} else if (isLoadMore) {
|
||||
setIsLoadingMore(true);
|
||||
} else {
|
||||
setLoading(true);
|
||||
}
|
||||
setError(null);
|
||||
const communityItems = useMemo(() => {
|
||||
return baseCommunityItems.map((item, index) => ({
|
||||
...item,
|
||||
id: `${activeCategory}-${index}`,
|
||||
}));
|
||||
}, [activeCategory]);
|
||||
|
||||
const response = await getTemplates({
|
||||
page,
|
||||
size: 10,
|
||||
status: 'RELEASE'
|
||||
});
|
||||
|
||||
if (isRefreshing || page === 1) {
|
||||
setTemplates(response.data);
|
||||
setCurrentPage(1);
|
||||
} else {
|
||||
setTemplates(prev => [...prev, ...response.data]);
|
||||
setCurrentPage(page);
|
||||
}
|
||||
|
||||
setHasMore(response.pagination.page < response.pagination.totalPages);
|
||||
} catch (err: any) {
|
||||
console.error('获取模板列表失败:', err);
|
||||
setError(err.message || '获取模板列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
setIsLoadingMore(false);
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
if (!isLoadingMore && hasMore && !refreshing) {
|
||||
fetchTemplates(currentPage + 1, false, true);
|
||||
}
|
||||
}, [currentPage, hasMore, isLoadingMore, refreshing, fetchTemplates]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
fetchTemplates();
|
||||
setShowLoginModal(false);
|
||||
}
|
||||
|
||||
Animated.loop(
|
||||
Animated.sequence([
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 1,
|
||||
duration: 2000,
|
||||
useNativeDriver: false,
|
||||
}),
|
||||
Animated.timing(pulseAnim, {
|
||||
toValue: 0,
|
||||
duration: 2000,
|
||||
useNativeDriver: false,
|
||||
}),
|
||||
])
|
||||
).start();
|
||||
|
||||
Animated.loop(
|
||||
Animated.sequence([
|
||||
Animated.timing(bounceAnim, {
|
||||
toValue: -10,
|
||||
duration: 600,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(bounceAnim, {
|
||||
toValue: -5,
|
||||
duration: 200,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(bounceAnim, {
|
||||
toValue: 0,
|
||||
duration: 600,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(bounceAnim, {
|
||||
toValue: 0,
|
||||
duration: 200,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
])
|
||||
).start();
|
||||
}, [isAuthenticated, fetchTemplates, pulseAnim, bounceAnim]);
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
fetchTemplates(1, true, false);
|
||||
}, [fetchTemplates]);
|
||||
|
||||
const handleTemplatePress = (template: Template) => {
|
||||
Alert.alert('模板详情', `您选择了: ${template.title}`);
|
||||
};
|
||||
|
||||
const handleVideoChange = (template: Template, index: number) => {
|
||||
console.log('视频切换到:', template.title, '索引:', index);
|
||||
};
|
||||
|
||||
if (!isAuthenticated) {
|
||||
const scaleAnim = pulseAnim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0.95, 1.05],
|
||||
const handleGeneratePress = () => {
|
||||
requireAuth(() => {
|
||||
console.log('用户已登录,执行生成操作');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
<View style={styles.welcomeContainer}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.iconContainer,
|
||||
{ transform: [{ scale: scaleAnim }] },
|
||||
]}
|
||||
>
|
||||
<Ionicons name="albums-outline" size={80} color="#007AFF" />
|
||||
</Animated.View>
|
||||
|
||||
<ThemedText type="title" style={styles.welcomeTitle}>
|
||||
欢迎使用模板中心
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.welcomeSubtitle}>
|
||||
登录后即可浏览和使用海量精美模板
|
||||
</ThemedText>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.loginButton}
|
||||
onPress={() => setShowLoginModal(true)}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#007AFF', '#0056D2']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.loginButtonGradient}
|
||||
>
|
||||
<Ionicons name="log-in-outline" size={20} color="#fff" style={styles.loginButtonIcon} />
|
||||
<Text style={styles.loginButtonText}>立即登录</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles.guestButton} activeOpacity={0.6}>
|
||||
<Text style={styles.guestButtonText}>游客浏览</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<LoginModal
|
||||
visible={showLoginModal}
|
||||
onClose={() => setShowLoginModal(false)}
|
||||
/>
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
const shadowOpacity = pulseAnim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0.3, 0.4],
|
||||
});
|
||||
const handleFeaturePress = (item: FeatureItemType) => {
|
||||
requireAuth(() => {
|
||||
console.log('用户已登录,使用功能:', item.title);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
<Animated.View style={[styles.headerBanner, { shadowOpacity }]}>
|
||||
<LinearGradient
|
||||
colors={['#ff6b6b', '#ff8e53', '#ff6b35']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.gradientBanner}
|
||||
>
|
||||
<Text style={styles.bannerText}>
|
||||
New User 50% Off Coupon : <Text style={styles.couponText}>NEWUSER</Text>
|
||||
</Text>
|
||||
<Animated.Text
|
||||
style={[
|
||||
styles.giftIcon,
|
||||
{ transform: [{ translateY: bounceAnim }] },
|
||||
]}
|
||||
>
|
||||
🎁
|
||||
</Animated.Text>
|
||||
</LinearGradient>
|
||||
</Animated.View>
|
||||
|
||||
<TemplateList
|
||||
templates={templates}
|
||||
loading={loading}
|
||||
refreshing={refreshing}
|
||||
isLoadingMore={isLoadingMore}
|
||||
hasMore={hasMore}
|
||||
onRefresh={handleRefresh}
|
||||
onLoadMore={loadMore}
|
||||
onTemplatePress={handleTemplatePress}
|
||||
onVideoChange={handleVideoChange}
|
||||
error={error}
|
||||
/>
|
||||
|
||||
<LoginModal
|
||||
visible={showLoginModal}
|
||||
onClose={() => setShowLoginModal(false)}
|
||||
/>
|
||||
</ThemedView>
|
||||
<PageLayout backgroundColor="#050505">
|
||||
<ScrollView
|
||||
style={styles.scroll}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<StatusBarSpacer />
|
||||
<Header />
|
||||
<CategoryTabs categories={categories} activeId={activeCategory} onChange={setActiveCategory} />
|
||||
<FeatureCarousel items={featureItems} onPress={handleFeaturePress} />
|
||||
<SectionHeader title="WAN 2.5 COMMUNITY" />
|
||||
<CommunityGrid items={communityItems} onPressAction={handleGeneratePress} />
|
||||
<View style={styles.bottomSpacer} />
|
||||
</ScrollView>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
scroll: {
|
||||
flex: 1,
|
||||
},
|
||||
welcomeContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 40,
|
||||
contentContainer: {
|
||||
paddingTop: 16,
|
||||
paddingBottom: 48,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
backgroundColor: 'rgba(0, 122, 255, 0.1)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 24,
|
||||
},
|
||||
welcomeTitle: {
|
||||
fontSize: 28,
|
||||
fontWeight: '700',
|
||||
marginBottom: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
welcomeSubtitle: {
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
opacity: 0.7,
|
||||
marginBottom: 48,
|
||||
lineHeight: 24,
|
||||
},
|
||||
loginButton: {
|
||||
width: '100%',
|
||||
height: 56,
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
elevation: 8,
|
||||
shadowColor: '#007AFF',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowRadius: 12,
|
||||
shadowOpacity: 0.3,
|
||||
},
|
||||
loginButtonGradient: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
},
|
||||
loginButtonIcon: {
|
||||
marginRight: 8,
|
||||
},
|
||||
loginButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 17,
|
||||
fontWeight: '700',
|
||||
},
|
||||
guestButton: {
|
||||
width: '100%',
|
||||
height: 56,
|
||||
borderRadius: 16,
|
||||
borderWidth: 1.5,
|
||||
borderColor: 'rgba(0, 122, 255, 0.3)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 12,
|
||||
},
|
||||
guestButtonText: {
|
||||
color: '#007AFF',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
headerBanner: {
|
||||
position: 'absolute',
|
||||
top: 16,
|
||||
left: '10%',
|
||||
right: '10%',
|
||||
zIndex: 1000,
|
||||
borderRadius: 20,
|
||||
shadowColor: '#ff6b6b',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowRadius: 12,
|
||||
elevation: 8,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
gradientBanner: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 4,
|
||||
paddingHorizontal: 16,
|
||||
position: 'relative',
|
||||
},
|
||||
bannerText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
},
|
||||
couponText: {
|
||||
fontWeight: '800',
|
||||
fontSize: 14,
|
||||
color: '#fff',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 6,
|
||||
marginLeft: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.6)',
|
||||
borderStyle: 'dashed',
|
||||
},
|
||||
giftIcon: {
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
fontSize: 16,
|
||||
bottomSpacer: {
|
||||
height: 32,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user