import React, { useRef, useState } from 'react'; import { View, Text, StyleSheet, Dimensions, Image, ScrollView, } from 'react-native'; import { FontAwesome } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { BlurView } from 'expo-blur'; import ViewShot from 'react-native-view-shot'; import { BannerGeneratorProps, BannerTemplate, LayoutType } from '../types'; import { DEFAULT_BANNER_TEMPLATES } from '../constants/templates'; import { ShareHelpers } from '../utils/shareHelpers'; const { width: SCREEN_WIDTH } = Dimensions.get('window'); const BANNER_WIDTH = SCREEN_WIDTH - 40; const BANNER_HEIGHT = 480; const BannerGenerator: React.FC = ({ data, profile, template = DEFAULT_BANNER_TEMPLATES[0], layoutType = 'stats', graphData, onBannerGenerated, }) => { const viewShotRef = useRef(null); const [isGenerating, setIsGenerating] = useState(false); const generateBanner = async (): Promise => { try { setIsGenerating(true); const uri = await viewShotRef.current?.capture?.(); onBannerGenerated?.(uri || ''); return uri || null; } catch (error) { console.error('Error generating banner:', error); return null; } finally { setIsGenerating(false); } }; const renderStatsSection = () => { if (layoutType === 'graph' && graphData) { return ( {/* Reading Graph */} {Array.from({ length: 5 }, (_, rowIndex) => ( {Array.from({ length: 6 }, (_, colIndex) => { const dayIndex = rowIndex * 6 + colIndex; const day = graphData.days[dayIndex]; if (!day) return ; return ( ); })} ))} {/* Graph Legend */} Less More ); } // Default stats layout return ( Total Pages {data.pagesThisMonth} % Goal {data.goalPercentage}% Books Read {data.booksCompletedThisMonth} Streak {data.readingStreak} ); }; const renderBanner = () => ( Level {profile ? ShareHelpers.getReaderLevelName(profile.readerLevel) : 'Novice'} {data.totalPoints.toLocaleString()} Points {/* Avatar */} {/* Hair */} {/* Face */} {/* Eyes */} {/* Nose */} {/* Mouth */} {/* Shirt */} {profile?.name || 'Reader'} {/* Books section */} Books Reading This Month {layoutType === 'graph' && graphData && ( {graphData.activeDays} days )} {data.booksReadThisMonth && data.booksReadThisMonth.length > 0 ? ( data.booksReadThisMonth.slice(0, 3).map((book, index) => ( 📖 {book.title} {Math.min(book.currentPage, book.totalPages)}p read )) ) : ( 📕 No books this month )} {data.booksReadThisMonth && data.booksReadThisMonth.length > 3 && ( +{data.booksReadThisMonth.length - 3} more )} {/* Conditional Stats/Graph Section */} {renderStatsSection()} {/* App branding at bottom */} 📚 RubixScript ); return ( {renderBanner()} ); }; const styles = StyleSheet.create({ container: { alignSelf: 'center', }, cardContainer: { alignSelf: 'center', padding: 10, }, card: { width: BANNER_WIDTH, height: BANNER_HEIGHT + 140, borderRadius: 20, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 20 }, shadowOpacity: 0.8, shadowRadius: 30, elevation: 25, position: 'relative', borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.1)', }, dynamicHeight: { height: BANNER_HEIGHT + 120, }, additional: { position: 'absolute', width: '100%', height: '100%', zIndex: 2, borderRadius: 20, overflow: 'hidden', }, glassBackground: { position: 'absolute', width: '100%', height: '100%', borderRadius: 20, }, glassContent: { position: 'absolute', width: '100%', height: '100%', padding: 20, borderRadius: 20, backgroundColor: 'transparent', }, userCard: { alignItems: 'center', marginBottom: 20, }, level: { backgroundColor: 'rgba(100, 200, 255, 0.2)', borderRadius: 15, paddingHorizontal: 15, paddingVertical: 8, marginBottom: 10, borderWidth: 1, borderColor: 'rgba(100, 200, 255, 0.4)', }, levelText: { color: '#64c8ff', fontSize: 14, fontWeight: '700', textShadowColor: 'rgba(0,0,0,0.5)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, points: { backgroundColor: 'rgba(255, 215, 0, 0.2)', borderRadius: 15, paddingHorizontal: 15, paddingVertical: 8, marginBottom: 15, borderWidth: 1, borderColor: 'rgba(255, 215, 0, 0.4)', }, pointsText: { color: '#ffd700', fontSize: 14, fontWeight: '700', textShadowColor: 'rgba(0,0,0,0.5)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, avatarContainer: { width: 110, height: 110, borderRadius: 55, backgroundColor: 'rgba(176, 210, 229, 0.3)', marginBottom: 5, overflow: 'hidden', justifyContent: 'center', alignItems: 'center', borderWidth: 2, borderColor: 'rgba(255, 255, 255, 0.2)', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 8, }, avatarBackground: { width: '100%', height: '100%', backgroundColor: 'rgba(176, 210, 229, 0.6)', justifyContent: 'center', alignItems: 'center', position: 'relative', }, headContainer: { width: 90, height: 90, position: 'relative', justifyContent: 'center', alignItems: 'center', }, hair: { position: 'absolute', top: 5, width: 65, height: 45, borderTopLeftRadius: 32, borderTopRightRadius: 32, borderBottomLeftRadius: 8, borderBottomRightRadius: 8, }, face: { width: 55, height: 55, borderRadius: 27, position: 'relative', justifyContent: 'center', alignItems: 'center', marginTop: 20, }, eyesContainer: { flexDirection: 'row', gap: 15, marginTop: -5, marginBottom: 8, }, eye: { width: 5, height: 5, borderRadius: 2.5, }, nose: { width: 3, height: 3, backgroundColor: '#d4a574', borderRadius: 1.5, marginBottom: 8, }, mouth: { width: 20, height: 8, borderTopLeftRadius: 10, borderTopRightRadius: 10, borderBottomLeftRadius: 10, borderBottomRightRadius: 10, }, shirt: { position: 'absolute', bottom: -10, width: 70, height: 40, borderTopLeftRadius: 15, borderTopRightRadius: 15, }, collar: { position: 'absolute', top: 0, left: '50%', marginLeft: -10, width: 20, height: 8, backgroundColor: '#fff', borderBottomLeftRadius: 10, borderBottomRightRadius: 10, }, buttons: { position: 'absolute', top: 12, left: '50%', marginLeft: -2, alignItems: 'center', gap: 4, }, button: { width: 4, height: 4, borderRadius: 2, }, moreInfo: { flex: 1, }, userName: { fontSize: 24, fontWeight: '800', color: '#ffffff', textAlign: 'center', marginBottom: 10, textShadowColor: 'rgba(0,0,0,0.7)', textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4, }, booksSection: { marginBottom: 15, }, booksSectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10, }, booksSectionTitle: { fontSize: 16, color: '#e0e0e0', fontWeight: '700', marginBottom: 10, textAlign: 'center', textShadowColor: 'rgba(0,0,0,0.7)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, bookItem: { flexDirection: 'row', alignItems: 'center', marginBottom: 6, paddingHorizontal: 5, gap: 8, }, bookEmoji: { fontSize: 14, }, bookInfo: { flex: 1, }, bookTitle: { fontSize: 13, color: '#ffffff', fontWeight: '600', textShadowColor: 'rgba(0,0,0,0.5)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, bookPages: { fontSize: 11, color: '#64c8ff', fontWeight: '700', backgroundColor: 'rgba(100, 200, 255, 0.15)', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 8, borderWidth: 1, borderColor: 'rgba(100, 200, 255, 0.3)', }, moreBooks: { fontSize: 12, color: '#cccccc', fontStyle: 'italic', textAlign: 'center', marginTop: 5, textShadowColor: 'rgba(0,0,0,0.5)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, stats: { flexDirection: 'row', justifyContent: 'space-around', marginTop: 20, paddingTop: 15, borderTopWidth: 1, borderTopColor: 'rgba(255, 255, 255, 0.2)', }, statItem: { alignItems: 'center', gap: 4, }, statTitle: { fontSize: 10, fontWeight: '700', color: '#e0e0e0', textTransform: 'uppercase', letterSpacing: 0.5, textShadowColor: 'rgba(0,0,0,0.7)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, statValue: { fontSize: 16, fontWeight: '800', color: '#ffffff', textShadowColor: 'rgba(0,0,0,0.7)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 3, }, appBranding: { alignItems: 'center', marginTop: 10, paddingTop: 10, paddingBottom: 5, borderTopWidth: 1, borderTopColor: 'rgba(255, 255, 255, 0.2)', }, appIconContainer: { width: 48, height: 48, borderRadius: 24, backgroundColor: 'rgba(100, 200, 255, 0.2)', justifyContent: 'center', alignItems: 'center', marginBottom: 6, borderWidth: 1, borderColor: 'rgba(100, 200, 255, 0.4)', shadowColor: '#64c8ff', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.3, shadowRadius: 4, elevation: 5, }, appIconText: { fontSize: 24, }, appName: { fontSize: 11, color: '#64c8ff', fontWeight: '700', letterSpacing: 0.8, textTransform: 'uppercase', textShadowColor: 'rgba(0,0,0,0.8)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 3, }, daysBadge: { backgroundColor: 'rgba(100, 200, 255, 0.2)', borderRadius: 12, paddingHorizontal: 10, paddingVertical: 4, borderWidth: 1, borderColor: 'rgba(100, 200, 255, 0.4)', }, daysBadgeText: { fontSize: 11, color: '#64c8ff', fontWeight: '700', textShadowColor: 'rgba(0,0,0,0.5)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, // Graph styles graphSection: { marginTop: 10, }, graphContainer: { marginBottom: 15, alignItems: 'center', }, graphGrid: { gap: 2, marginBottom: 8, alignItems: 'center', }, graphRow: { flexDirection: 'row', gap: 2, }, graphDay: { width: 10, height: 10, borderRadius: 2.5, borderWidth: 0.5, borderColor: 'rgba(255,255,255,0.1)', }, graphDayEmpty: { width: 10, height: 10, borderRadius: 2.5, backgroundColor: 'transparent', }, graphDayToday: { borderColor: 'rgba(100,200,255,1)', borderWidth: 1.5, }, graphLegend: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6, }, graphLegendText: { fontSize: 9, color: '#e0e0e0', fontWeight: '500', }, graphLegendScale: { flexDirection: 'row', gap: 2, }, graphLegendBox: { width: 12, height: 12, borderRadius: 2.5, borderWidth: 0.5, borderColor: 'rgba(255,255,255,0.1)', }, }); export default BannerGenerator;