247 lines
6.6 KiB
TypeScript
247 lines
6.6 KiB
TypeScript
import { TemplateList } from '@/components/templates/template-list';
|
|
import { ThemedText } from '@/components/themed-text';
|
|
import { ThemedView } from '@/components/themed-view';
|
|
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 { useCallback, useEffect, useRef, useState } from 'react';
|
|
import { Alert, Animated, StyleSheet, Text } from 'react-native';
|
|
|
|
export default function HomeScreen() {
|
|
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 pulseAnim = useRef(new Animated.Value(0)).current;
|
|
const bounceAnim = useRef(new Animated.Value(0)).current;
|
|
|
|
const fetchTemplates = useCallback(async (page: number = 1, isRefreshing = false, isLoadMore = false) => {
|
|
if (!isAuthenticated) return;
|
|
|
|
try {
|
|
if (isRefreshing) {
|
|
setRefreshing(true);
|
|
} else if (isLoadMore) {
|
|
setIsLoadingMore(true);
|
|
} else {
|
|
setLoading(true);
|
|
}
|
|
setError(null);
|
|
|
|
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();
|
|
}
|
|
|
|
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) {
|
|
return (
|
|
<ThemedView style={styles.container}>
|
|
<ThemedView style={styles.centerContent}>
|
|
<ThemedText type="title" style={styles.title}>欢迎使用</ThemedText>
|
|
<ThemedText style={styles.subtitle}>请先登录以查看模板列表</ThemedText>
|
|
</ThemedView>
|
|
</ThemedView>
|
|
);
|
|
}
|
|
|
|
const shadowOpacity = pulseAnim.interpolate({
|
|
inputRange: [0, 1],
|
|
outputRange: [0.3, 0.4],
|
|
});
|
|
|
|
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}
|
|
/>
|
|
</ThemedView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
},
|
|
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,
|
|
},
|
|
centerContent: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: 20,
|
|
},
|
|
title: {
|
|
marginBottom: 8,
|
|
textAlign: 'center',
|
|
},
|
|
subtitle: {
|
|
textAlign: 'center',
|
|
opacity: 0.7,
|
|
},
|
|
});
|