183 lines
4.4 KiB
TypeScript
183 lines
4.4 KiB
TypeScript
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',
|
|
},
|
|
});
|