// app/sync-progress.tsx — Full-screen active sync progress import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, SafeAreaView, Animated, Easing, } from 'react-native'; import { router, useLocalSearchParams } from 'expo-router'; import { CheckCircle, RefreshCw } from 'lucide-react-native'; type SyncPhase = | 'connecting' | 'sending' | 'receiving' | 'done' | 'error'; export default function SyncProgressScreen() { const params = useLocalSearchParams<{ partnerName?: string; sentCount?: string; receivedCount?: string; conflictCount?: string; error?: string; }>(); const [phase, setPhase] = useState('connecting'); const [sendProgress] = useState(new Animated.Value(0)); const [receiveProgress] = useState(new Animated.Value(0)); const spinAnim = useState(new Animated.Value(0))[0]; const sentCount = parseInt(params.sentCount ?? '0', 10); const receivedCount = parseInt(params.receivedCount ?? '0', 10); const conflictCount = parseInt(params.conflictCount ?? '0', 10); const partnerName = params.partnerName ?? 'Partner'; const errorMsg = params.error; useEffect(() => { if (errorMsg) { setPhase('error'); return; } // Simulate sync animation sequence const connectTimer = setTimeout(() => { setPhase('sending'); Animated.timing(sendProgress, { toValue: 1, duration: 1200, useNativeDriver: false, easing: Easing.out(Easing.cubic), }).start(() => { setPhase('receiving'); Animated.timing(receiveProgress, { toValue: 1, duration: 1200, useNativeDriver: false, easing: Easing.out(Easing.cubic), }).start(() => { setPhase('done'); }); }); }, 1000); return () => clearTimeout(connectTimer); }, []); // Spinning icon animation useEffect(() => { if (phase === 'connecting' || phase === 'sending' || phase === 'receiving') { Animated.loop( Animated.timing(spinAnim, { toValue: 1, duration: 1000, useNativeDriver: true, easing: Easing.linear, }) ).start(); } else { spinAnim.stopAnimation(); } }, [phase]); const spin = spinAnim.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'], }); const sendWidth = sendProgress.interpolate({ inputRange: [0, 1], outputRange: ['0%', '100%'], }); const receiveWidth = receiveProgress.interpolate({ inputRange: [0, 1], outputRange: ['0%', '100%'], }); const phaseLabel = { connecting: `Connecting to ${partnerName}…`, sending: `Sending records…`, receiving: `Receiving records…`, done: 'Sync Complete!', error: 'Sync Failed', }[phase]; return ( {/* Icon */} {phase === 'done' ? ( ) : phase === 'error' ? ( ) : ( )} {/* Phase label */} {phaseLabel} {errorMsg ? ( {errorMsg} ) : null} {/* Progress bars */} {phase !== 'done' && phase !== 'error' && ( Sending {sentCount} Receiving {receivedCount} )} {/* Summary card */} {phase === 'done' && ( Sync Summary Partner {partnerName} Sent {sentCount} records Received {receivedCount} records {conflictCount > 0 && ( Conflicts {conflictCount} resolved )} )} {/* Done button */} {(phase === 'done' || phase === 'error') && ( router.back()} > {phase === 'done' ? 'Done' : 'Go Back'} )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#0F172A', }, content: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 24, gap: 24, }, iconArea: { width: 100, height: 100, justifyContent: 'center', alignItems: 'center', }, errorCircle: { width: 64, height: 64, borderRadius: 32, backgroundColor: '#FEE2E2', justifyContent: 'center', alignItems: 'center', }, errorX: { fontSize: 28, color: '#EF4444', fontWeight: '700', }, phaseLabel: { fontSize: 22, fontWeight: '700', color: '#F9FAFB', textAlign: 'center', }, errorDetail: { fontSize: 14, color: '#F87171', textAlign: 'center', paddingHorizontal: 16, }, progressArea: { width: '100%', gap: 16, backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 16, padding: 20, }, progressRow: { flexDirection: 'row', alignItems: 'center', gap: 10, }, progressLabel: { color: '#9CA3AF', fontSize: 13, width: 72, }, progressBar: { flex: 1, height: 8, backgroundColor: 'rgba(255,255,255,0.1)', borderRadius: 4, overflow: 'hidden', }, progressFill: { height: '100%', borderRadius: 4, }, sendFill: { backgroundColor: '#1A6B72', }, receiveFill: { backgroundColor: '#10B981', }, progressCount: { color: '#D1D5DB', fontSize: 13, width: 32, textAlign: 'right', }, summaryCard: { width: '100%', backgroundColor: 'rgba(255,255,255,0.06)', borderRadius: 16, padding: 20, gap: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)', }, summaryTitle: { color: '#F9FAFB', fontSize: 16, fontWeight: '700', marginBottom: 4, }, summaryRow: { flexDirection: 'row', justifyContent: 'space-between', }, summaryKey: { color: '#9CA3AF', fontSize: 14, }, summaryValue: { color: '#F9FAFB', fontSize: 14, fontWeight: '600', }, sentColor: { color: '#1A6B72' }, receivedColor: { color: '#10B981' }, conflictColor: { color: '#F59E0B' }, doneBtn: { paddingVertical: 14, paddingHorizontal: 48, backgroundColor: '#1A6B72', borderRadius: 14, marginTop: 8, }, errorBtn: { backgroundColor: '#6B7280', }, doneBtnText: { color: '#fff', fontSize: 16, fontWeight: '700', }, });