import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { router } from 'expo-router'; import React, { useMemo, useState } from 'react'; import { Image, type ImageSourcePropType, ScrollView, StyleSheet, TouchableOpacity, View, useWindowDimensions, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { ThemedText } from '@/components/themed-text'; import { useAuth } from '@/hooks/use-auth'; import { useColorScheme } from '@/hooks/use-color-scheme'; type TabKey = 'all' | 'image' | 'video'; type BillingMode = 'monthly' | 'lifetime'; type Palette = { background: string; surface: string; elevated: string; border: string; pill: string; textPrimary: string; textSecondary: string; accent: string; onAccent: string; tabActive: string; avatarBackdrop: string; glyphBackdrop: string; }; type IdentityStat = { key: string; label: string; value: number; }; const TABS: { key: TabKey; label: string }[] = [ { key: 'all', label: 'All' }, { key: 'image', label: 'Image' }, { key: 'video', label: 'Video' }, ]; const BILLING_OPTIONS: { key: BillingMode; label: string }[] = [ { key: 'monthly', label: '月付' }, { key: 'lifetime', label: '终身' }, ]; const palettes: Record<'dark' | 'light', Palette> = { dark: { background: '#050505', surface: '#121216', elevated: '#101014', border: '#1D1E24', pill: '#16171C', textPrimary: '#F6F7FA', textSecondary: '#8E9098', accent: '#B7FF2F', onAccent: '#050505', tabActive: '#FFFFFF', avatarBackdrop: '#1F2026', glyphBackdrop: '#18181D', }, light: { background: '#F7F8FB', surface: '#FFFFFF', elevated: '#F0F2F8', border: '#E2E5ED', pill: '#E8EBF4', textPrimary: '#0F1320', textSecondary: '#5E6474', accent: '#405CFF', onAccent: '#FFFFFF', tabActive: '#FFFFFF', avatarBackdrop: '#D5D8E2', glyphBackdrop: '#E8EBF4', }, }; const STAT_BLUEPRINT: IdentityStat[] = [ { key: 'likes', label: 'likes', value: 0 }, { key: 'posts', label: 'posts', value: 0 }, { key: 'views', label: 'views', value: 0 }, ]; export default function ProfileScreen() { const { user } = useAuth(); const colorScheme = useColorScheme(); const palette = palettes[colorScheme === 'dark' ? 'dark' : 'light']; const insets = useSafeAreaInsets(); const { width } = useWindowDimensions(); const [billingMode, setBillingMode] = useState('monthly'); const [activeTab, setActiveTab] = useState('all'); const displayName = deriveDisplayName(user) ?? 'prairie_pufferfish_the'; const avatarSource = deriveAvatarSource(user); const creditBalance = deriveNumericValue((user as Record)?.credits); const stats = useMemo(() => createStats(user), [user]); const horizontalPadding = width < 400 ? 20 : 24; const avatarSize = width < 400 ? 74 : 82; return ( {insets.top > 0 && } ); } function HeaderRow({ palette, billingMode, onChangeBilling, credits, }: { palette: Palette; billingMode: BillingMode; onChangeBilling: (mode: BillingMode) => void; credits: number; }) { return ( router.push('/settings/account')} style={[styles.settingsButton, { backgroundColor: palette.pill }]} activeOpacity={0.85} > ); } function BillingBadge({ palette, billingMode, onChangeBilling, credits, }: { palette: Palette; billingMode: BillingMode; onChangeBilling: (mode: BillingMode) => void; credits: number; }) { return ( {BILLING_OPTIONS.map(option => { const isActive = option.key === billingMode; return ( onChangeBilling(option.key)} activeOpacity={0.85} style={[ styles.billingOption, { backgroundColor: isActive ? palette.tabActive : 'transparent', }, ]} > {option.label} ); })} {credits} ); } function IdentitySection({ palette, displayName, avatarSource, avatarSize, stats, }: { palette: Palette; displayName: string; avatarSource?: ImageSourcePropType; avatarSize: number; stats: IdentityStat[]; }) { return ( {displayName} router.push('/settings/account')} activeOpacity={0.85} style={[ styles.editButton, { borderColor: palette.border, backgroundColor: palette.surface, }, ]} > Edit {stats.map(stat => ( ))} ); } function Avatar({ palette, size, source, fallback, }: { palette: Palette; size: number; source?: ImageSourcePropType; fallback: string; }) { const initials = getInitials(fallback); return ( {source ? ( ) : ( {initials} )} ); } function StatItem({ palette, label, value }: { palette: Palette; label: string; value: number }) { return ( {value} {label} ); } function Divider({ palette }: { palette: Palette }) { return ; } function ContentTabs({ palette, activeTab, onChangeTab, }: { palette: Palette; activeTab: TabKey; onChangeTab: (tab: TabKey) => void; }) { return ( {TABS.map(tab => { const isActive = tab.key === activeTab; return ( onChangeTab(tab.key)} style={[ styles.tabButton, { backgroundColor: isActive ? palette.tabActive : 'transparent', }, ]} > {tab.label} ); })} ); } function EmptyGallery({ palette, activeTab }: { palette: Palette; activeTab: TabKey }) { const copy = deriveEmptyCopy(activeTab); return ( {copy.title} {copy.subtitle} router.push('/(tabs)/explore')} style={[ styles.generateButton, { borderColor: palette.accent, backgroundColor: palette.elevated, }, ]} > Generate ); } function deriveEmptyCopy(activeTab: TabKey) { if (activeTab === 'image') { return { title: 'No Images yet', subtitle: 'Pick a style, enter a prompt, and generate your image.', }; } if (activeTab === 'video') { return { title: 'No Videos yet', subtitle: 'Pick a style, enter a prompt, and craft your first clip.', }; } return { title: 'No Content yet', subtitle: 'Pick a style, enter a prompt, and generate your image.', }; } function createStats(user: unknown): IdentityStat[] { const source = (user as Record) ?? {}; return STAT_BLUEPRINT.map(stat => ({ ...stat, value: deriveNumericValue(source[stat.key]), })); } function deriveDisplayName(user: unknown): string | null { if (!user || typeof user !== 'object') { return null; } const data = user as Record; return ensureString(data.username) ?? ensureString(data.name) ?? null; } function deriveAvatarSource(user: unknown): ImageSourcePropType | undefined { if (!user || typeof user !== 'object') { return undefined; } const image = ensureString((user as Record).image); if (!image) { return undefined; } return { uri: image }; } function ensureString(value: unknown): string | null { if (typeof value !== 'string') { return null; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function deriveNumericValue(value: unknown): number { if (typeof value === 'number' && Number.isFinite(value)) { return value; } if (typeof value === 'string') { const parsed = Number(value); if (Number.isFinite(parsed)) { return parsed; } } return 0; } function getInitials(name: string): string { const tokens = name .replace(/[_-]+/g, ' ') .split(/\s+/) .filter(Boolean); if (tokens.length === 0) { return 'AI'; } const initials = tokens.slice(0, 2).map(part => part[0]?.toUpperCase() ?? ''); return initials.join('') || tokens[0][0]?.toUpperCase() || 'AI'; } const styles = StyleSheet.create({ screen: { flex: 1, }, scroll: { flexGrow: 1, }, headerRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 28, }, settingsButton: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center', }, billingShell: { flexDirection: 'row', alignItems: 'center', padding: 4, borderWidth: 1, borderRadius: 999, }, billingOptions: { flexDirection: 'row', alignItems: 'center', }, billingOption: { paddingHorizontal: 16, paddingVertical: 6, borderRadius: 999, }, billingLabel: { fontSize: 13, fontWeight: '600', }, lightningShell: { flexDirection: 'row', alignItems: 'center', borderWidth: 1, borderRadius: 999, paddingHorizontal: 12, paddingVertical: 6, marginLeft: 8, }, lightningValue: { fontSize: 13, fontWeight: '600', marginLeft: 6, }, identityRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 24, }, avatarShell: { alignItems: 'center', justifyContent: 'center', }, avatarInitials: { fontSize: 28, fontWeight: '700', }, identityDetails: { flex: 1, marginLeft: 18, }, nameRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 10, }, username: { flex: 1, fontSize: 20, fontWeight: '700', marginRight: 12, }, editButton: { flexDirection: 'row', alignItems: 'center', borderRadius: 999, borderWidth: 1, paddingHorizontal: 20, paddingVertical: 8, }, editIcon: { marginRight: 6, }, editLabel: { fontSize: 14, fontWeight: '600', }, statRow: { flexDirection: 'row', alignItems: 'center', }, statItem: { flexDirection: 'row', alignItems: 'baseline', marginRight: 18, }, statValue: { fontSize: 15, fontWeight: '700', marginRight: 4, }, statLabel: { fontSize: 13, fontWeight: '500', textTransform: 'lowercase', }, divider: { height: 1, marginVertical: 24, }, tabsBar: { flexDirection: 'row', alignItems: 'center', borderWidth: 1, borderRadius: 999, padding: 4, }, tabButton: { flex: 1, borderRadius: 999, paddingVertical: 10, alignItems: 'center', justifyContent: 'center', }, tabLabel: { fontSize: 15, fontWeight: '600', }, emptyWrap: { alignItems: 'center', paddingTop: 32, }, emptyGlyph: { width: 112, height: 112, borderRadius: 28, borderWidth: 1, alignItems: 'center', justifyContent: 'center', marginBottom: 24, }, emptyTitle: { fontSize: 18, fontWeight: '700', marginBottom: 6, }, emptySubtitle: { fontSize: 14, lineHeight: 20, textAlign: 'center', marginBottom: 28, paddingHorizontal: 24, }, generateButton: { borderWidth: 1, borderRadius: 999, paddingHorizontal: 28, paddingVertical: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, generateIcon: { marginRight: 8, }, generateLabel: { fontSize: 15, fontWeight: '700', }, });