Files
2026-07-13 13:05:33 +08:00

168 lines
5.5 KiB
TypeScript

import { View, Text, StyleSheet, Pressable, Linking, Platform } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useRouter } from 'expo-router'
import { ChevronLeft, Globe } from 'lucide-react-native'
import Svg, { Path } from 'react-native-svg'
import Constants from 'expo-constants'
import { OrcaLogo } from '../src/components/OrcaLogo'
import { colors, spacing, typography } from '../src/theme/mobile-theme'
// Why: read version + native build identifier from expo-constants at
// runtime so the About screen never drifts out of sync with app.json.
// nativeBuildVersion is iOS buildNumber on iOS and versionCode on
// Android — different concepts, same role (monotonic native build id).
function getVersionLabel(): string {
const version = Constants.expoConfig?.version ?? '?.?.?'
const build =
Platform.OS === 'ios'
? Constants.expoConfig?.ios?.buildNumber
: String(Constants.expoConfig?.android?.versionCode ?? '')
return build ? `v${version} (${build})` : `v${version}`
}
function GithubIcon({ size = 16, color = colors.textSecondary }) {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill={color}>
<Path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
</Svg>
)
}
function XIcon({ size = 16, color = colors.textSecondary }) {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill={color}>
<Path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</Svg>
)
}
export default function AboutScreen() {
const router = useRouter()
const insets = useSafeAreaInsets()
return (
<View style={[styles.container, { paddingTop: insets.top + spacing.sm }]}>
<View style={styles.topRow}>
<Pressable style={styles.backButton} onPress={() => router.back()}>
<ChevronLeft size={22} color={colors.textSecondary} />
</Pressable>
<Text style={styles.heading}>About</Text>
</View>
<View style={styles.brand}>
<OrcaLogo size={28} />
<Text style={styles.brandName}>Orca</Text>
<Text style={styles.brandSub}>Open-source agent IDE for 100x builders</Text>
</View>
<View style={styles.section}>
<Pressable
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
onPress={() => void Linking.openURL('https://onOrca.dev')}
>
<Globe size={16} color={colors.textSecondary} />
<Text style={styles.rowValue}>onOrca.dev</Text>
</Pressable>
<View style={styles.separator} />
<Pressable
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
onPress={() => void Linking.openURL('https://github.com/stablyai/orca')}
>
<GithubIcon />
<Text style={styles.rowValue}>stablyai/orca</Text>
</Pressable>
<View style={styles.separator} />
<Pressable
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
onPress={() => void Linking.openURL('https://x.com/orca_build')}
>
<XIcon />
<Text style={styles.rowValue}>@orca_build</Text>
</Pressable>
</View>
<Text style={styles.versionText}>{getVersionLabel()}</Text>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.bgBase,
padding: spacing.lg
},
topRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: spacing.xl
},
backButton: {
width: 36,
height: 36,
borderRadius: 18,
alignItems: 'center',
justifyContent: 'center',
marginRight: spacing.sm
},
heading: {
fontSize: 20,
fontWeight: '700',
color: colors.textPrimary
},
brand: {
alignItems: 'center',
paddingVertical: spacing.xl,
marginBottom: spacing.lg
},
brandName: {
fontSize: 22,
fontWeight: '800',
color: colors.textPrimary,
marginTop: spacing.sm
},
brandSub: {
fontSize: 13,
color: colors.textMuted,
marginTop: spacing.xs
},
section: {
backgroundColor: colors.bgPanel,
borderRadius: 12,
overflow: 'hidden'
},
row: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm + 2,
paddingVertical: spacing.md,
paddingHorizontal: spacing.md + 2
},
rowPressed: {
backgroundColor: colors.bgRaised
},
rowLabel: {
flex: 1,
fontSize: typography.bodySize,
fontWeight: '500',
color: colors.textPrimary
},
rowValue: {
flex: 1,
textAlign: 'right',
fontSize: typography.bodySize,
color: colors.textSecondary
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: colors.borderSubtle,
marginHorizontal: spacing.md
},
versionText: {
marginTop: spacing.lg,
textAlign: 'center',
fontSize: typography.metaSize,
color: colors.textMuted
}
})