import { HeaderBar } from '@/components/ui/HeaderBar'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import type { BadgeDto } from '@/services/badges'; import { fetchAvailableBadges, selectBadgesLoading, selectSortedBadges } from '@/store/badgesSlice'; import { DEFAULT_MEMBER_NAME, selectUserProfile } from '@/store/userSlice'; import { BadgeShowcaseModal } from '@/components/badges/BadgeShowcaseModal'; import { Toast } from '@/utils/toast.utils'; import { Ionicons } from '@expo/vector-icons'; import { useFocusEffect } from '@react-navigation/native'; import * as Haptics from 'expo-haptics'; import { Image } from 'expo-image'; import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FlatList, Pressable, RefreshControl, StyleSheet, Text, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; export default function BadgesScreen() { const dispatch = useAppDispatch(); const { t } = useTranslation(); const insets = useSafeAreaInsets(); const badges = useAppSelector(selectSortedBadges); const loading = useAppSelector(selectBadgesLoading); const userProfile = useAppSelector(selectUserProfile); const [refreshing, setRefreshing] = useState(false); const [showcaseBadge, setShowcaseBadge] = useState(null); useFocusEffect( useCallback(() => { dispatch(fetchAvailableBadges()); }, [dispatch]) ); const handleRefresh = useCallback(async () => { setRefreshing(true); try { await dispatch(fetchAvailableBadges()).unwrap(); } catch (error: any) { const message = typeof error === 'string' ? error : error?.message ?? 'Failed to refresh badges'; Toast.error(message); } finally { setRefreshing(false); } }, [dispatch]); const gridData = useMemo(() => badges, [badges]); const handleBadgePress = useCallback(async (badge: BadgeDto) => { try { await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); } catch { // Best-effort haptics; ignore if unavailable } setShowcaseBadge(badge); }, []); const renderBadgeTile = ({ item }: { item: BadgeDto }) => { const isAwarded = item.isAwarded; return ( [styles.badgeTile, pressed && styles.badgeTilePressed]} onPress={() => handleBadgePress(item)} accessibilityRole="button" > {item.imageUrl ? ( ) : ( {item.icon ?? '🏅'} )} {!isAwarded && ( )} {item.name} {item.description} {isAwarded ? t('badges.status.earned') : t('badges.status.locked')} ); }; const headerOffset = insets.top + 64; return ( item.code} numColumns={3} contentContainerStyle={[ styles.listContent, { paddingTop: headerOffset, paddingBottom: insets.bottom + 24 }, ]} columnWrapperStyle={styles.columnWrapper} renderItem={renderBadgeTile} ListHeaderComponent={null} ListEmptyComponent={ {t('badges.empty.title')} {t('badges.empty.description')} } refreshControl={ } showsVerticalScrollIndicator={false} /> setShowcaseBadge(null)} username={userProfile?.name && userProfile.name.trim() ? userProfile.name : DEFAULT_MEMBER_NAME} appName="Out Live" /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#ffffff', }, listContent: { paddingHorizontal: 16, minHeight: '100%', backgroundColor: '#ffffff' }, columnWrapper: { justifyContent: 'space-between', marginBottom: 16, }, badgeTile: { flex: 1, marginHorizontal: 4, padding: 12, alignItems: 'center', borderRadius: 16, backgroundColor: '#FFFFFF', shadowColor: '#0F172A', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.06, shadowRadius: 12, elevation: 2, }, badgeTilePressed: { transform: [{ scale: 0.97 }], shadowOpacity: 0.02, }, badgeImageContainer: { width: 88, height: 88, borderRadius: 28, overflow: 'hidden', marginBottom: 10, position: 'relative', }, badgeImageEarned: { borderWidth: 1.5, borderColor: 'rgba(16,185,129,0.6)', }, badgeImageLocked: { borderWidth: 1.5, borderColor: 'rgba(148,163,184,0.5)', }, badgeImage: { width: '100%', height: '100%', }, badgeOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(15,23,42,0.45)', alignItems: 'center', justifyContent: 'center', }, badgeImageFallback: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#F3F4F6', }, badgeImageFallbackText: { fontSize: 36, }, badgeTitle: { fontSize: 14, fontWeight: '700', color: '#111827', }, badgeDescription: { fontSize: 12, color: '#6B7280', textAlign: 'center', marginTop: 4, }, badgeStatus: { fontSize: 11, fontWeight: '600', marginTop: 6, }, badgeStatusEarned: { color: '#0F766E', }, badgeStatusLocked: { color: '#9CA3AF', }, emptyState: { alignItems: 'center', padding: 32, borderRadius: 24, backgroundColor: '#FFFFFF', marginTop: 24, shadowColor: '#000', shadowOffset: { width: 0, height: 6 }, shadowOpacity: 0.08, shadowRadius: 12, elevation: 3, }, emptyStateTitle: { fontSize: 18, fontWeight: '700', color: '#0F172A', }, emptyStateDescription: { fontSize: 14, color: '#475467', textAlign: 'center', marginTop: 8, lineHeight: 20, }, });