Blame · Line-by-line history
CommitsScreen.tsx
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 9a8fbed | 1 | import React, { useCallback } from 'react'; |
| 2 | import { FlatList, StyleSheet, Text, View } from 'react-native'; | |
| 3 | import { SafeAreaView } from 'react-native-safe-area-context'; | |
| 4 | import type { NativeStackScreenProps } from '@react-navigation/native-stack'; | |
| 5 | import { useCommits } from '../hooks/useRepo'; | |
| 6 | import { CommitRow } from '../components/CommitRow'; | |
| 7 | import { LoadingSpinner } from '../components/LoadingSpinner'; | |
| 8 | import { ErrorBanner } from '../components/ErrorBanner'; | |
| 9 | import { colors } from '../theme/colors'; | |
| 10 | import type { RepoStackParamList } from '../navigation/AppNavigator'; | |
| 11 | import type { CommitEntry } from '../api/repos'; | |
| 12 | ||
| 13 | type Props = NativeStackScreenProps<RepoStackParamList, 'Commits'>; | |
| 14 | ||
| 15 | export function CommitsScreen({ route }: Props): React.ReactElement { | |
| 16 | const { owner, repo, branch } = route.params; | |
| 17 | const { commits, isLoading, error, loadMore, hasMore } = useCommits( | |
| 18 | owner, | |
| 19 | repo, | |
| 20 | branch ?? 'HEAD', | |
| 21 | ); | |
| 22 | ||
| 23 | const renderCommit = useCallback( | |
| 24 | ({ item }: { item: CommitEntry }) => <CommitRow commit={item} />, | |
| 25 | [], | |
| 26 | ); | |
| 27 | ||
| 28 | const keyExtractor = useCallback((item: CommitEntry) => item.sha, []); | |
| 29 | ||
| 30 | const renderFooter = useCallback(() => { | |
| 31 | if (!hasMore) return null; | |
| 32 | return <LoadingSpinner size="small" />; | |
| 33 | }, [hasMore]); | |
| 34 | ||
| 35 | if (isLoading && commits.length === 0) return <LoadingSpinner fullScreen />; | |
| 36 | ||
| 37 | return ( | |
| 38 | <SafeAreaView style={styles.safe} edges={['bottom']}> | |
| 39 | {error !== null && <ErrorBanner message={error} />} | |
| 40 | <FlatList | |
| 41 | data={commits} | |
| 42 | keyExtractor={keyExtractor} | |
| 43 | renderItem={renderCommit} | |
| 44 | onEndReached={loadMore} | |
| 45 | onEndReachedThreshold={0.3} | |
| 46 | ListFooterComponent={renderFooter} | |
| 47 | ListEmptyComponent={ | |
| 48 | <View style={styles.emptyState}> | |
| 49 | <Text style={styles.emptyText}>No commits found</Text> | |
| 50 | </View> | |
| 51 | } | |
| 52 | ListHeaderComponent={ | |
| 53 | <View style={styles.header}> | |
| 54 | <Text style={styles.headerText}> | |
| 55 | {branch ?? 'HEAD'} — commit history | |
| 56 | </Text> | |
| 57 | </View> | |
| 58 | } | |
| 59 | style={styles.list} | |
| 60 | /> | |
| 61 | </SafeAreaView> | |
| 62 | ); | |
| 63 | } | |
| 64 | ||
| 65 | const styles = StyleSheet.create({ | |
| 66 | safe: { | |
| 67 | flex: 1, | |
| 68 | backgroundColor: colors.bg, | |
| 69 | }, | |
| 70 | list: { | |
| 71 | flex: 1, | |
| 72 | }, | |
| 73 | header: { | |
| 74 | padding: 12, | |
| 75 | backgroundColor: colors.bgTertiary, | |
| 76 | borderBottomWidth: 1, | |
| 77 | borderBottomColor: colors.border, | |
| 78 | }, | |
| 79 | headerText: { | |
| 80 | fontSize: 12, | |
| 81 | fontFamily: 'monospace', | |
| 82 | color: colors.textMuted, | |
| 83 | }, | |
| 84 | emptyState: { | |
| 85 | padding: 40, | |
| 86 | alignItems: 'center', | |
| 87 | }, | |
| 88 | emptyText: { | |
| 89 | fontSize: 14, | |
| 90 | color: colors.textMuted, | |
| 91 | }, | |
| 92 | }); |