Initial commit: expo-popcore project
This commit is contained in:
182
components/templates/template-list.tsx
Normal file
182
components/templates/template-list.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import { useThemeColor } from '@/hooks/use-theme-color';
|
||||
import { Template } from '@/lib/types/template';
|
||||
import { ActivityIndicator, FlatList, RefreshControl, StyleSheet, View } from 'react-native';
|
||||
import { ThemedText } from '../themed-text';
|
||||
import { ThemedView } from '../themed-view';
|
||||
import { TemplateCard } from './template-card';
|
||||
|
||||
interface TemplateListProps {
|
||||
templates: Template[];
|
||||
loading?: boolean;
|
||||
refreshing?: boolean;
|
||||
isLoadingMore?: boolean;
|
||||
hasMore?: boolean;
|
||||
onRefresh?: () => void;
|
||||
onLoadMore?: () => void;
|
||||
onTemplatePress?: (template: Template) => void;
|
||||
onVideoChange?: (template: Template, index: number) => void;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export function TemplateList({
|
||||
templates,
|
||||
loading = false,
|
||||
refreshing = false,
|
||||
isLoadingMore = false,
|
||||
hasMore = true,
|
||||
onRefresh,
|
||||
onLoadMore,
|
||||
onTemplatePress,
|
||||
onVideoChange,
|
||||
error,
|
||||
}: TemplateListProps) {
|
||||
const tintColor = useThemeColor({}, 'tint');
|
||||
const errorColor = useThemeColor({}, 'error');
|
||||
|
||||
// 渲染底部加载指示器
|
||||
const renderFooter = () => {
|
||||
if (isLoadingMore) {
|
||||
return (
|
||||
<View style={styles.footerLoader}>
|
||||
<ActivityIndicator size="small" color={tintColor} />
|
||||
<ThemedText style={styles.footerText}>加载中...</ThemedText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasMore && templates.length > 0) {
|
||||
return (
|
||||
<View style={styles.noMoreContainer}>
|
||||
<ThemedText style={styles.noMoreText}>— 没有更多了 —</ThemedText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
if (loading && templates.length === 0) {
|
||||
return (
|
||||
<ThemedView style={styles.centerContainer}>
|
||||
<ActivityIndicator size="large" color={tintColor} />
|
||||
<ThemedText style={styles.loadingText}>加载中...</ThemedText>
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ThemedView style={styles.centerContainer}>
|
||||
<ThemedText style={[styles.errorText, { color: errorColor }]}>
|
||||
❌ {error}
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
if (templates.length === 0) {
|
||||
return (
|
||||
<ThemedView style={styles.centerContainer}>
|
||||
<ThemedText style={styles.emptyText}>暂无模板</ThemedText>
|
||||
{!hasMore && (
|
||||
<View style={styles.noMoreContainer}>
|
||||
<ThemedText style={styles.noMoreText}>— 没有更多了 —</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={templates}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item, index }) => (
|
||||
<TemplateCard
|
||||
template={item}
|
||||
onPress={onTemplatePress}
|
||||
allTemplates={templates}
|
||||
currentIndex={index}
|
||||
onVideoChange={onVideoChange}
|
||||
/>
|
||||
)}
|
||||
numColumns={2}
|
||||
columnWrapperStyle={styles.row}
|
||||
contentContainerStyle={styles.listContent}
|
||||
refreshControl={
|
||||
onRefresh ? (
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={[tintColor]}
|
||||
tintColor={tintColor}
|
||||
progressViewOffset={70}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
onEndReached={onLoadMore}
|
||||
onEndReachedThreshold={0.2}
|
||||
ListFooterComponent={renderFooter}
|
||||
ListFooterComponentStyle={templates.length > 0 ? styles.footerContainer : null}
|
||||
showsVerticalScrollIndicator={false}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={10}
|
||||
initialNumToRender={6}
|
||||
windowSize={10}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
centerContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
listContent: {
|
||||
paddingHorizontal: 14,
|
||||
paddingTop: 70,
|
||||
paddingBottom: 80,
|
||||
},
|
||||
row: {
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 12,
|
||||
fontSize: 16,
|
||||
opacity: 0.7,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 16,
|
||||
opacity: 0.7,
|
||||
},
|
||||
footerLoader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 20,
|
||||
gap: 8,
|
||||
},
|
||||
footerText: {
|
||||
fontSize: 14,
|
||||
opacity: 0.7,
|
||||
},
|
||||
footerContainer: {
|
||||
paddingBottom: 20,
|
||||
},
|
||||
noMoreContainer: {
|
||||
paddingVertical: 20,
|
||||
alignItems: 'center',
|
||||
},
|
||||
noMoreText: {
|
||||
fontSize: 14,
|
||||
opacity: 0.5,
|
||||
color: '#666',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user