mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-07 19:40:20 +00:00
376 lines
18 KiB
JavaScript
376 lines
18 KiB
JavaScript
import React, { useState } from 'react';
|
|
import {
|
|
StyleSheet, View, Text, ScrollView, TouchableOpacity, Alert,
|
|
ActivityIndicator, Switch, TextInput,
|
|
} from 'react-native';
|
|
import { useTheme } from '../services/ThemeContext';
|
|
import DatabaseService from '../services/DatabaseService';
|
|
import GoogleAUTH from '../AUTH/GoogleAUTH';
|
|
import NASAUTH from '../AUTH/NASAUTH';
|
|
import NASSetupInstructions from '../components/NASSetupInstructions';
|
|
import ServiceHealthIndicator from '../components/ServiceHealthIndicator';
|
|
|
|
const OnboardingScreen = ({ navigation, route }) => {
|
|
const { userId } = route.params;
|
|
const { theme } = useTheme();
|
|
const [currentStep, setCurrentStep] = useState(0);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
// Step 0 — service toggles
|
|
const [googleEnabled, setGoogleEnabled] = useState(false);
|
|
const [iosEnabled, setIosEnabled] = useState(false);
|
|
const [nasEnabled, setNasEnabled] = useState(false);
|
|
|
|
// Step 1 — connection status
|
|
const [googleConnected, setGoogleConnected] = useState(false);
|
|
const [iosConnected, setIosConnected] = useState(false);
|
|
const [nasConnected, setNasConnected] = useState(false);
|
|
|
|
// Step 1 — health indicators
|
|
const [googleHealth, setGoogleHealth] = useState('inactive');
|
|
const [iosHealth, setIosHealth] = useState('inactive');
|
|
const [nasHealth, setNasHealth] = useState('inactive');
|
|
|
|
// NAS fields
|
|
const [nasHost, setNasHost] = useState('');
|
|
const [nasPort, setNasPort] = useState('9500');
|
|
const [nasUsername, setNasUsername] = useState('');
|
|
|
|
// Step 2 — auto-backup checklist
|
|
const [autoBackupServices, setAutoBackupServices] = useState([]);
|
|
|
|
const steps = [
|
|
{ title: 'Choose Services', description: 'Select which photo services to connect (1-3)' },
|
|
{ title: 'Connect Services', description: 'Set up each selected service' },
|
|
{ title: 'Automatic Backup', description: 'Choose where new photos are backed up' },
|
|
];
|
|
|
|
const getConnectedServices = () => {
|
|
const services = [];
|
|
if (googleEnabled && googleConnected) services.push({ name: 'Google Photos', type: 'GOOGLE_PHOTOS', icon: '📷' });
|
|
if (iosEnabled && iosConnected) services.push({ name: 'iCloud Photos', type: 'ICLOUD_PHOTOS', icon: '🍎' });
|
|
if (nasEnabled && nasConnected) services.push({ name: 'NAS Storage', type: 'NAS', icon: '💾' });
|
|
return services;
|
|
};
|
|
|
|
const handleGoogleAuth = async () => {
|
|
setLoading(true);
|
|
setGoogleHealth('checking');
|
|
try {
|
|
const googleAuth = new GoogleAUTH();
|
|
const result = await googleAuth.authenticate();
|
|
await DatabaseService.linkAccount(userId, 'GOOGLE_PHOTOS', {
|
|
accessToken: result.accessToken,
|
|
refreshToken: result.refreshToken || null,
|
|
expiresAt: googleAuth.expiresAt || Date.now() + 3600000,
|
|
email: result.email || 'google-user',
|
|
});
|
|
setGoogleConnected(true);
|
|
setGoogleHealth('healthy');
|
|
Alert.alert('Success', 'Google Photos connected!');
|
|
} catch (error) {
|
|
setGoogleHealth('error');
|
|
Alert.alert('Error', 'Failed to connect Google Photos: ' + error.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleIosAuth = async () => {
|
|
setLoading(true);
|
|
setIosHealth('checking');
|
|
try {
|
|
await DatabaseService.linkAccount(userId, 'ICLOUD_PHOTOS', {
|
|
status: 'placeholder',
|
|
note: 'iCloud integration pending Apple API access',
|
|
});
|
|
setIosConnected(true);
|
|
setIosHealth('healthy');
|
|
Alert.alert('Info', 'iCloud Photos registered. Full integration coming soon.');
|
|
} catch (error) {
|
|
setIosHealth('error');
|
|
Alert.alert('Error', 'Failed to set up iCloud: ' + error.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleNasAuth = async () => {
|
|
if (!nasHost.trim() || !nasUsername.trim()) {
|
|
Alert.alert('Error', 'Please fill in the NAS host and username');
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
setNasHealth('checking');
|
|
try {
|
|
const nasAuth = new NASAUTH();
|
|
await nasAuth.authenticate(nasHost.trim(), parseInt(nasPort) || 9500, nasUsername.trim());
|
|
await DatabaseService.linkAccount(userId, 'NAS', {
|
|
host: nasHost.trim(),
|
|
port: parseInt(nasPort) || 9500,
|
|
username: nasUsername.trim(),
|
|
});
|
|
setNasConnected(true);
|
|
setNasHealth('healthy');
|
|
Alert.alert('Success', 'NAS connected!');
|
|
} catch (error) {
|
|
setNasHealth('error');
|
|
Alert.alert('Error', 'Failed to connect NAS: ' + error.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const toggleAutoBackup = (serviceType) => {
|
|
setAutoBackupServices((prev) =>
|
|
prev.includes(serviceType) ? prev.filter((s) => s !== serviceType) : [...prev, serviceType]
|
|
);
|
|
};
|
|
|
|
const handleNext = async () => {
|
|
if (currentStep === 0) {
|
|
if (!googleEnabled && !iosEnabled && !nasEnabled) {
|
|
Alert.alert('Error', 'Please select at least one service');
|
|
return;
|
|
}
|
|
setCurrentStep(1);
|
|
} else if (currentStep === 1) {
|
|
const anyConnected =
|
|
(googleEnabled && googleConnected) ||
|
|
(iosEnabled && iosConnected) ||
|
|
(nasEnabled && nasConnected);
|
|
if (!anyConnected) { Alert.alert('Error', 'Please connect at least one service'); return; }
|
|
setCurrentStep(2);
|
|
} else if (currentStep === 2) {
|
|
try {
|
|
setLoading(true);
|
|
await DatabaseService.setAutoBackupServices(userId, autoBackupServices);
|
|
navigation.replace('MainTabs', { userId });
|
|
} catch (error) {
|
|
Alert.alert('Error', 'Failed to complete setup: ' + error.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<View style={[styles.container, { backgroundColor: theme.background }]}>
|
|
{/* Progress bar */}
|
|
<View style={styles.progressBar}>
|
|
{steps.map((_, i) => (
|
|
<View key={i} style={[styles.progressDot, { backgroundColor: i <= currentStep ? theme.primary : theme.border }]} />
|
|
))}
|
|
</View>
|
|
|
|
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
|
<View style={styles.header}>
|
|
<Text style={[styles.stepLabel, { color: theme.primary }]}>Step {currentStep + 1} of {steps.length}</Text>
|
|
<Text style={[styles.title, { color: theme.text }]}>{steps[currentStep].title}</Text>
|
|
<Text style={[styles.description, { color: theme.textSecondary }]}>{steps[currentStep].description}</Text>
|
|
</View>
|
|
|
|
{/* ── Step 0: pick services ── */}
|
|
{currentStep === 0 && (
|
|
<View style={styles.cardsContainer}>
|
|
<ServiceToggleCard theme={theme} icon="📷" name="Google Photos"
|
|
description="Access your Google Photos library"
|
|
enabled={googleEnabled} onToggle={() => setGoogleEnabled(!googleEnabled)} />
|
|
<ServiceToggleCard theme={theme} icon="🍎" name="iCloud Photos"
|
|
description="Access your iCloud photo library (placeholder)"
|
|
enabled={iosEnabled} onToggle={() => setIosEnabled(!iosEnabled)} />
|
|
<ServiceToggleCard theme={theme} icon="💾" name="NAS Storage"
|
|
description="Connect to your NAS photo server (REST API)"
|
|
enabled={nasEnabled} onToggle={() => setNasEnabled(!nasEnabled)} />
|
|
{nasEnabled && <NASSetupInstructions />}
|
|
</View>
|
|
)}
|
|
|
|
{/* ── Step 1: connect each service ── */}
|
|
{currentStep === 1 && (
|
|
<View style={styles.cardsContainer}>
|
|
|
|
{googleEnabled && (
|
|
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
<View style={styles.configHeader}>
|
|
<ServiceHealthIndicator status={googleHealth} size={10} />
|
|
<Text style={styles.configIcon}>📷</Text>
|
|
<Text style={[styles.configTitle, { color: theme.text }]}>Google Photos</Text>
|
|
{googleConnected && <Text style={[styles.connectedBadge, { color: theme.accent }]}>✓ Connected</Text>}
|
|
</View>
|
|
{!googleConnected && (
|
|
<TouchableOpacity
|
|
style={[styles.authButton, { backgroundColor: '#4285F4' }, loading && styles.buttonDisabled]}
|
|
onPress={handleGoogleAuth} disabled={loading}
|
|
>
|
|
{loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.authButtonText}>Sign in with Google</Text>}
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
)}
|
|
|
|
{iosEnabled && (
|
|
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
<View style={styles.configHeader}>
|
|
<ServiceHealthIndicator status={iosHealth} size={10} />
|
|
<Text style={styles.configIcon}>🍎</Text>
|
|
<Text style={[styles.configTitle, { color: theme.text }]}>iCloud Photos</Text>
|
|
{iosConnected && <Text style={[styles.connectedBadge, { color: theme.accent }]}>✓ Connected</Text>}
|
|
</View>
|
|
{!iosConnected && (
|
|
<TouchableOpacity
|
|
style={[styles.authButton, { backgroundColor: '#333' }, loading && styles.buttonDisabled]}
|
|
onPress={handleIosAuth} disabled={loading}
|
|
>
|
|
{loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.authButtonText}>Connect iCloud</Text>}
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
)}
|
|
|
|
{nasEnabled && (
|
|
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
<View style={styles.configHeader}>
|
|
<ServiceHealthIndicator status={nasHealth} size={10} />
|
|
<Text style={styles.configIcon}>💾</Text>
|
|
<Text style={[styles.configTitle, { color: theme.text }]}>NAS Storage</Text>
|
|
{nasConnected && <Text style={[styles.connectedBadge, { color: theme.accent }]}>✓ Connected</Text>}
|
|
</View>
|
|
{!nasConnected && (
|
|
<>
|
|
<Text style={[styles.fieldLabel, { color: theme.textSecondary }]}>Server Address</Text>
|
|
<TextInput style={[styles.fieldInput, { backgroundColor: theme.inputBg, borderColor: theme.border, color: theme.text }]}
|
|
placeholder="192.168.1.100" placeholderTextColor={theme.textTertiary}
|
|
value={nasHost} onChangeText={setNasHost} editable={!loading} />
|
|
<Text style={[styles.fieldLabel, { color: theme.textSecondary }]}>Port</Text>
|
|
<TextInput style={[styles.fieldInput, { backgroundColor: theme.inputBg, borderColor: theme.border, color: theme.text }]}
|
|
placeholder="9500" placeholderTextColor={theme.textTertiary}
|
|
value={nasPort} onChangeText={setNasPort} editable={!loading} keyboardType="numeric" />
|
|
<Text style={[styles.fieldLabel, { color: theme.textSecondary }]}>Username</Text>
|
|
<TextInput style={[styles.fieldInput, { backgroundColor: theme.inputBg, borderColor: theme.border, color: theme.text }]}
|
|
placeholder="Your username" placeholderTextColor={theme.textTertiary}
|
|
value={nasUsername} onChangeText={setNasUsername} editable={!loading} autoCapitalize="none" />
|
|
<TouchableOpacity
|
|
style={[styles.authButton, { backgroundColor: '#0a7ea4' }, loading && styles.buttonDisabled]}
|
|
onPress={handleNasAuth} disabled={loading}
|
|
>
|
|
{loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.authButtonText}>Test & Connect</Text>}
|
|
</TouchableOpacity>
|
|
</>
|
|
)}
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
|
|
{/* ── Step 2: auto-backup ── */}
|
|
{currentStep === 2 && (
|
|
<View style={styles.cardsContainer}>
|
|
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
<Text style={[styles.checklistInfo, { color: theme.textSecondary }]}>
|
|
Select which services new photos should automatically upload to. You can select multiple or none.
|
|
</Text>
|
|
{getConnectedServices().map((svc) => (
|
|
<TouchableOpacity key={svc.type}
|
|
style={[styles.checkRow, { borderBottomColor: theme.borderLight }]}
|
|
onPress={() => toggleAutoBackup(svc.type)} activeOpacity={0.7}
|
|
>
|
|
<View style={[styles.checkbox, { borderColor: theme.primary },
|
|
autoBackupServices.includes(svc.type) && { backgroundColor: theme.primary }]}>
|
|
{autoBackupServices.includes(svc.type) && <Text style={styles.checkmark}>✓</Text>}
|
|
</View>
|
|
<Text style={styles.checkIcon}>{svc.icon}</Text>
|
|
<Text style={[styles.checkLabel, { color: theme.text }]}>{svc.name}</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
<TouchableOpacity style={[styles.checkRow, { borderBottomWidth: 0 }]}
|
|
onPress={() => setAutoBackupServices([])} activeOpacity={0.7}>
|
|
<View style={[styles.checkbox, { borderColor: theme.border },
|
|
autoBackupServices.length === 0 && { backgroundColor: theme.primary, borderColor: theme.primary }]}>
|
|
{autoBackupServices.length === 0 && <Text style={styles.checkmark}>✓</Text>}
|
|
</View>
|
|
<Text style={[styles.checkLabel, { color: theme.text }]}>Don't automatically back up</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
)}
|
|
</ScrollView>
|
|
|
|
{/* Footer */}
|
|
<View style={[styles.footer, { borderTopColor: theme.border }]}>
|
|
{currentStep > 0 ? (
|
|
<TouchableOpacity style={[styles.backButton, { backgroundColor: theme.surface, borderColor: theme.border }]}
|
|
onPress={() => setCurrentStep(currentStep - 1)} disabled={loading}>
|
|
<Text style={[styles.backButtonText, { color: theme.textSecondary }]}>Back</Text>
|
|
</TouchableOpacity>
|
|
) : <View style={{ flex: 1 }} />}
|
|
<TouchableOpacity
|
|
style={[styles.nextButton, { backgroundColor: theme.primary }, loading && styles.buttonDisabled]}
|
|
onPress={handleNext} disabled={loading}
|
|
>
|
|
{loading ? <ActivityIndicator color="#fff" /> : (
|
|
<Text style={styles.nextButtonText}>{currentStep === steps.length - 1 ? 'Get Started' : 'Next'}</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const ServiceToggleCard = ({ theme, icon, name, description, enabled, onToggle }) => (
|
|
<View style={[styles.toggleCard, { backgroundColor: theme.card, borderColor: enabled ? theme.primary : theme.border }]}>
|
|
<View style={styles.toggleCardContent}>
|
|
<Text style={styles.toggleIcon}>{icon}</Text>
|
|
<View style={styles.toggleInfo}>
|
|
<Text style={[styles.toggleName, { color: theme.text }]}>{name}</Text>
|
|
<Text style={[styles.toggleDescription, { color: theme.textSecondary }]}>{description}</Text>
|
|
</View>
|
|
</View>
|
|
<Switch value={enabled} onValueChange={onToggle}
|
|
trackColor={{ false: theme.border, true: '#a8dab5' }}
|
|
thumbColor={enabled ? '#34a853' : '#f4f3f4'} />
|
|
</View>
|
|
);
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1 },
|
|
progressBar: { flexDirection: 'row', justifyContent: 'center', gap: 8, paddingTop: 60, paddingBottom: 8 },
|
|
progressDot: { width: 32, height: 4, borderRadius: 2 },
|
|
content: { flex: 1, paddingHorizontal: 20 },
|
|
header: { marginBottom: 24 },
|
|
stepLabel: { fontSize: 12, fontWeight: '600', marginBottom: 6 },
|
|
title: { fontSize: 26, fontWeight: '700', marginBottom: 6 },
|
|
description: { fontSize: 14 },
|
|
cardsContainer: { gap: 12 },
|
|
toggleCard: { borderRadius: 12, padding: 16, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderWidth: 1.5 },
|
|
toggleCardContent: { flex: 1, flexDirection: 'row', alignItems: 'center' },
|
|
toggleIcon: { fontSize: 28, marginRight: 14 },
|
|
toggleInfo: { flex: 1 },
|
|
toggleName: { fontSize: 15, fontWeight: '600', marginBottom: 2 },
|
|
toggleDescription: { fontSize: 12 },
|
|
configCard: { borderRadius: 12, padding: 16, borderWidth: 1 },
|
|
configHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 },
|
|
configIcon: { fontSize: 24, marginRight: 10 },
|
|
configTitle: { fontSize: 16, fontWeight: '600', flex: 1 },
|
|
connectedBadge: { fontSize: 13, fontWeight: '600' },
|
|
authButton: { borderRadius: 8, paddingVertical: 12, alignItems: 'center', marginTop: 4 },
|
|
authButtonText: { color: '#fff', fontSize: 14, fontWeight: '600' },
|
|
fieldLabel: { fontSize: 12, fontWeight: '600', marginTop: 10, marginBottom: 4 },
|
|
fieldInput: { borderWidth: 1, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 10, fontSize: 14 },
|
|
checklistInfo: { fontSize: 13, marginBottom: 8 },
|
|
checkRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 14, borderBottomWidth: StyleSheet.hairlineWidth },
|
|
checkbox: { width: 22, height: 22, borderRadius: 4, borderWidth: 2, justifyContent: 'center', alignItems: 'center', marginRight: 12 },
|
|
checkmark: { color: '#fff', fontSize: 14, fontWeight: '700' },
|
|
checkIcon: { fontSize: 18, marginRight: 8 },
|
|
checkLabel: { fontSize: 14, fontWeight: '500', flex: 1 },
|
|
footer: { flexDirection: 'row', paddingHorizontal: 20, paddingVertical: 14, borderTopWidth: 1, gap: 12 },
|
|
backButton: { flex: 1, paddingVertical: 12, borderRadius: 8, alignItems: 'center', borderWidth: 1 },
|
|
backButtonText: { fontSize: 15, fontWeight: '600' },
|
|
nextButton: { flex: 1, paddingVertical: 12, borderRadius: 8, alignItems: 'center' },
|
|
nextButtonText: { fontSize: 15, fontWeight: '600', color: '#fff' },
|
|
buttonDisabled: { opacity: 0.6 },
|
|
});
|
|
|
|
export default OnboardingScreen;
|
|
|