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,
},
});