import Ionicons from '@expo/vector-icons/Ionicons'; import { useRouter } from 'expo-router'; import React, { useCallback, useMemo, useState } from 'react'; import type { ListRenderItem } from 'react-native'; import { ActivityIndicator, FlatList, RefreshControl, StatusBar, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useBalance } from '@/hooks/use-balance'; import { useTransactions } from '@/hooks/use-transactions'; import type { Transaction, TransactionKind } from '@/lib/api/transactions'; type FilterKey = 'all' | TransactionKind; const screenPalette = { background: '#080808', surface: '#101010', surfaceActive: '#1C1C1C', surfaceOutline: '#1D1D1D', divider: '#161616', primaryText: '#F5F5F5', secondaryText: '#6C6C6C', accent: '#FEB840', accentHalo: 'rgba(254, 184, 64, 0.22)', negative: '#8F8F8F', }; const FILTERS: { key: FilterKey; label: string }[] = [ { key: 'all', label: 'All records' }, { key: 'consumed', label: 'Consumed' }, { key: 'obtained', label: 'Obtained' }, ]; export default function PointsDetailsScreen() { const insets = useSafeAreaInsets(); const router = useRouter(); const [activeFilter, setActiveFilter] = useState('all'); const { balance, isLoading: isBalanceLoading, refresh: refreshBalance } = useBalance(); const { transactions, isLoading: isTransactionsLoading, refresh: refreshTransactions, } = useTransactions(); const [refreshing, setRefreshing] = useState(false); const onRefresh = useCallback(async () => { setRefreshing(true); await Promise.all([refreshBalance(), refreshTransactions()]); setRefreshing(false); }, [refreshBalance, refreshTransactions]); const filteredEntries = useMemo(() => { if (activeFilter === 'all') { return transactions; } return transactions.filter(entry => entry.kind === activeFilter); }, [activeFilter, transactions]); const listContentStyle = useMemo( () => ({ paddingBottom: Math.max(insets.bottom + 36, 72), paddingTop: 4, }), [insets.bottom], ); const keyExtractor = useCallback((entry: Transaction) => entry.id, []); const renderSeparator = useCallback(() => , []); const renderLedgerEntry: ListRenderItem = useCallback(({ item }) => { const amount = Math.abs(item.amount); const isPositive = item.amount > 0; const displayDate = new Date(item.happenedAt).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); return ( {item.title} {displayDate} {isPositive ? `+ ${amount}` : `- ${amount}`} ); }, []); const isLoading = isBalanceLoading || isTransactionsLoading; return ( {isLoading && !refreshing ? ( ) : ( <> {balance.remainingTokenBalance} {FILTERS.map(filter => { const isActive = filter.key === activeFilter; return ( setActiveFilter(filter.key)} accessibilityRole="button" > {filter.label} ); })} } /> )} ); } const styles = StyleSheet.create({ screen: { flex: 1, backgroundColor: screenPalette.background, paddingHorizontal: 24, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, headerRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', minHeight: 48, }, iconButton: { width: 40, height: 40, borderRadius: 20, alignItems: 'center', justifyContent: 'center', }, headerTitle: { fontSize: 17, lineHeight: 24, fontWeight: '600', color: screenPalette.primaryText, letterSpacing: 0.2, }, balanceCluster: { alignItems: 'center', marginTop: 32, marginBottom: 28, gap: 10, }, energyOrb: { width: 60, height: 60, borderRadius: 30, backgroundColor: screenPalette.accentHalo, alignItems: 'center', justifyContent: 'center', shadowColor: screenPalette.accent, shadowOpacity: 0.3, shadowRadius: 16, shadowOffset: { width: 0, height: 8 }, elevation: 8, }, balanceValue: { fontSize: 46, lineHeight: 52, fontWeight: '700', color: screenPalette.primaryText, letterSpacing: 0.5, fontVariant: ['tabular-nums'], }, segmentRail: { flexDirection: 'row', backgroundColor: screenPalette.surface, padding: 4, borderRadius: 28, borderWidth: StyleSheet.hairlineWidth, borderColor: screenPalette.surfaceOutline, gap: 4, }, segmentChip: { flex: 1, borderRadius: 24, alignItems: 'center', justifyContent: 'center', paddingVertical: 11, }, segmentChipActive: { backgroundColor: screenPalette.surfaceActive, }, segmentLabel: { fontSize: 14, lineHeight: 20, color: screenPalette.secondaryText, fontWeight: '500', letterSpacing: 0.2, }, segmentLabelActive: { color: screenPalette.primaryText, }, list: { flex: 1, marginTop: 28, }, listDivider: { height: StyleSheet.hairlineWidth, backgroundColor: screenPalette.divider, marginVertical: 0, }, recordRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 14, }, recordTitle: { fontSize: 15, lineHeight: 22, fontWeight: '600', color: screenPalette.primaryText, }, recordTimestamp: { marginTop: 6, fontSize: 12, lineHeight: 18, color: screenPalette.secondaryText, letterSpacing: 0.2, }, recordValue: { fontSize: 16, lineHeight: 22, fontWeight: '600', fontVariant: ['tabular-nums'], }, valuePositive: { color: screenPalette.accent, }, valueNegative: { color: screenPalette.negative, }, });