import { useState, useEffect, useCallback } from 'react'; import { View, Text, FlatList, StyleSheet, TouchableOpacity, Alert, ActivityIndicator, RefreshControl, ScrollView, Platform, BackHandler } from 'react-native'; import { StatusBar } from 'expo-status-bar'; import { Ionicons } from '@expo/vector-icons'; import RepoItem from '../components/RepoItem'; import { fetchStarredRepos, TokenExpiredError } from '../services/github'; import { initDatabase, getAllCategories, saveRepos, getAllRepos, getReposByCategory, getUncategorizedRepos, getGitHubToken, batchSetRepoCategories } from '../services/database'; import { runAutoCategorize } from '../services/categorizer'; // 首页:仓库列表、分类标签栏、同步入口 export default function HomeScreen({ onTokenExpired, onOpenSettings, onOpenRepoDetail, onOpenCategoryManage }) { const [categories, setCategories] = useState([]); // selectedCategory: null=全部, 0=未分类, >0=具体分类 ID const [selectedCategory, setSelectedCategory] = useState(null); const [repos, setRepos] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [syncing, setSyncing] = useState(false); const [syncInfo, setSyncInfo] = useState(''); // 按分类加载仓库列表 const loadRepos = useCallback(async (catId) => { if (catId === null || catId === undefined) { const allRepos = await getAllRepos(); setRepos(allRepos); } else if (catId === 0) { const uncatRepos = await getUncategorizedRepos(); setRepos(uncatRepos); } else { const catRepos = await getReposByCategory(catId); setRepos(catRepos); } }, []); // 加载所有数据:分类 + 仓库列表 const loadData = useCallback(async () => { try { await initDatabase(); const cats = await getAllCategories(); setCategories(cats); await loadRepos(selectedCategory); } catch (e) { console.error('加载数据失败:', e); } finally { setLoading(false); } }, [selectedCategory, loadRepos]); useEffect(() => { loadData(); }, [loadData]); // Android 硬件返回按钮退出应用 useEffect(() => { const onBackPress = () => { Alert.alert('退出应用', '确定要退出吗?', [ { text: '取消', style: 'cancel' }, { text: '退出', style: 'destructive', onPress: () => BackHandler.exitApp() }, ]); return true; }; const subscription = BackHandler.addEventListener('hardwareBackPress', onBackPress); return () => subscription.remove(); }, []); const onSelectCategory = async (catId) => { setSelectedCategory(catId); await loadRepos(catId); }; // 从 GitHub 同步星标仓库,保存到本地后执行自动分类 const handleSync = async () => { const token = await getGitHubToken(); if (!token) { onTokenExpired(); return; } setSyncing(true); setSyncInfo('正在从 GitHub 获取星标仓库...'); try { const reposData = await fetchStarredRepos(token); const count = await saveRepos(reposData); const cats = await getAllCategories(); await runAutoCategorize(cats, getUncategorizedRepos, batchSetRepoCategories); setSyncInfo(`同步完成,新增 ${count} 个仓库(共 ${reposData.length} 个)`); setCategories(cats); await loadRepos(selectedCategory); setTimeout(() => setSyncInfo(''), 3000); } catch (e) { if (e instanceof TokenExpiredError) { onTokenExpired(); } else { Alert.alert('同步失败', e.message); } } finally { setSyncing(false); } }; const handleRefresh = async () => { setRefreshing(true); try { await handleSync(); } finally { setRefreshing(false); } }; // 长按仓库时提示去分类管理页操作 const handleRepoLongPress = () => { Alert.alert('管理分类', '请前往分类管理页面设置仓库分类', [ { text: '取消', style: 'cancel' }, { text: '前往', onPress: onOpenCategoryManage }, ]); }; const currentCategoryName = selectedCategory === null ? '全部仓库' : selectedCategory === 0 ? '未分类' : categories.find(c => c.id === selectedCategory)?.name || '全部仓库'; if (loading) { return ( 加载中... ); } return ( GitHub Stars {syncInfo ? ( {syncInfo} ) : null} onSelectCategory(null)} > 全部 onSelectCategory(0)} > 未分类 {categories.map((cat) => ( onSelectCategory(cat.id)} > {cat.name} ))} {currentCategoryName} {repos.length} 个仓库 {syncing ? ( 同步中... ) : null} ( )} keyExtractor={(item) => String(item.id)} refreshControl={ } ListEmptyComponent={ {selectedCategory === 0 ? '没有未分类的仓库' : '暂无仓库数据\n点击右上角 ☁️ 按钮从 GitHub 同步'} } contentContainerStyle={repos.length === 0 ? styles.emptyContainer : styles.listContent} /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, center: { flex: 1, justifyContent: 'center', alignItems: 'center', }, loadingText: { marginTop: 10, color: '#888', fontSize: 14, }, header: { backgroundColor: '#fff', paddingTop: Platform.OS === 'ios' ? 50 : 40, paddingBottom: 8, paddingHorizontal: 16, borderBottomWidth: 1, borderBottomColor: '#e8e8e8', }, headerTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, headerTitle: { fontSize: 24, fontWeight: '700', color: '#1a1a1a', }, headerActions: { flexDirection: 'row', gap: 8, }, headerBtn: { padding: 8, borderRadius: 8, backgroundColor: '#f0f7ff', }, syncInfo: { marginTop: 6, fontSize: 12, color: '#28a745', }, categoryTabs: { backgroundColor: '#fff', paddingVertical: 10, paddingLeft: 12, borderBottomWidth: 1, borderBottomColor: '#eee', }, categoryTab: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 7, marginRight: 8, borderRadius: 20, backgroundColor: '#f0f0f0', }, categoryTabActive: { backgroundColor: '#0366d6', }, categoryTabText: { fontSize: 13, color: '#666', fontWeight: '500', }, categoryTabTextActive: { color: '#fff', }, categoryDot: { width: 8, height: 8, borderRadius: 4, marginRight: 5, }, sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 10, }, sectionTitle: { fontSize: 16, fontWeight: '600', color: '#333', }, sectionCount: { fontSize: 13, color: '#999', }, syncingBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#0366d6', paddingVertical: 6, gap: 8, }, syncingText: { color: '#fff', fontSize: 13, }, listContent: { paddingBottom: 20, }, emptyContainer: { flexGrow: 1, justifyContent: 'center', alignItems: 'center', }, empty: { alignItems: 'center', paddingVertical: 40, }, emptyText: { marginTop: 12, fontSize: 14, color: '#999', textAlign: 'center', lineHeight: 20, }, });