mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-07 19:40:20 +00:00
feat: implement NAS image lazy-loading and enhance service connection UI
This commit is contained in:
1 parent
6167418074
commit
5158305086
8 files changed
+573
-492
No files matched your search
+30
-184
@@ -1,14 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
StyleSheet, View, Text, ScrollView, TouchableOpacity, Alert,
|
||||
ActivityIndicator, Switch, TextInput,
|
||||
ActivityIndicator, Switch,
|
||||
} 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';
|
||||
import ServiceConnectCard from '../components/ServiceConnectCard';
|
||||
|
||||
const OnboardingScreen = ({ navigation, route }) => {
|
||||
const { userId } = route.params;
|
||||
@@ -16,27 +13,17 @@ const OnboardingScreen = ({ navigation, route }) => {
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Service toggles
|
||||
// Step 0 — service toggles
|
||||
const [googleEnabled, setGoogleEnabled] = useState(false);
|
||||
const [iosEnabled, setIosEnabled] = useState(false);
|
||||
const [nasEnabled, setNasEnabled] = useState(false);
|
||||
|
||||
// Service connection status
|
||||
// Step 1 — track which services completed connection via ServiceConnectCard callbacks
|
||||
const [googleConnected, setGoogleConnected] = useState(false);
|
||||
const [iosConnected, setIosConnected] = useState(false);
|
||||
const [nasConnected, setNasConnected] = useState(false);
|
||||
|
||||
// Service health: 'inactive' | 'healthy' | 'error' | 'checking'
|
||||
const [googleHealth, setGoogleHealth] = useState('inactive');
|
||||
const [iosHealth, setIosHealth] = useState('inactive');
|
||||
const [nasHealth, setNasHealth] = useState('inactive');
|
||||
|
||||
// NAS config
|
||||
const [nasHost, setNasHost] = useState('');
|
||||
const [nasPort, setNasPort] = useState('9500');
|
||||
const [nasUsername, setNasUsername] = useState('');
|
||||
|
||||
// Auto-backup checklist (multi-select)
|
||||
// Step 2 — auto-backup checklist
|
||||
const [autoBackupServices, setAutoBackupServices] = useState([]);
|
||||
|
||||
const steps = [
|
||||
@@ -53,82 +40,6 @@ const OnboardingScreen = ({ navigation, route }) => {
|
||||
return services;
|
||||
};
|
||||
|
||||
const handleGoogleAuth = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setGoogleHealth('checking');
|
||||
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) {
|
||||
console.error('Google auth error:', error);
|
||||
setGoogleHealth('error');
|
||||
Alert.alert('Error', 'Failed to connect Google Photos: ' + error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIosAuth = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setIosHealth('checking');
|
||||
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) {
|
||||
console.error('iCloud auth error:', 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;
|
||||
}
|
||||
try {
|
||||
setLoading(true);
|
||||
setNasHealth('checking');
|
||||
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) {
|
||||
console.error('NAS auth error:', 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]
|
||||
@@ -137,8 +48,10 @@ const OnboardingScreen = ({ navigation, route }) => {
|
||||
|
||||
const handleNext = async () => {
|
||||
if (currentStep === 0) {
|
||||
const any = googleEnabled || iosEnabled || nasEnabled;
|
||||
if (!any) { Alert.alert('Error', 'Please select at least one service'); return; }
|
||||
if (!googleEnabled && !iosEnabled && !nasEnabled) {
|
||||
Alert.alert('Error', 'Please select at least one service');
|
||||
return;
|
||||
}
|
||||
setCurrentStep(1);
|
||||
} else if (currentStep === 1) {
|
||||
const anyConnected =
|
||||
@@ -188,84 +101,35 @@ const OnboardingScreen = ({ navigation, route }) => {
|
||||
<ServiceToggleCard theme={theme} icon="💾" name="NAS Storage"
|
||||
description="Connect to your NAS photo server (REST API)"
|
||||
enabled={nasEnabled} onToggle={() => setNasEnabled(!nasEnabled)} />
|
||||
|
||||
{/* NAS setup instructions dropdown */}
|
||||
{nasEnabled && <NASSetupInstructions />}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Step 1: Configure each service */}
|
||||
{/* Step 1: Configure each selected service via shared ServiceConnectCard */}
|
||||
{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: theme.primary }, loading && styles.buttonDisabled]}
|
||||
onPress={handleGoogleAuth} disabled={loading}
|
||||
>
|
||||
{loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.authButtonText}>Sign in with Google</Text>}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<ServiceConnectCard
|
||||
serviceType="GOOGLE_PHOTOS"
|
||||
userId={userId}
|
||||
isLinked={googleConnected}
|
||||
onLinked={() => setGoogleConnected(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{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>
|
||||
<ServiceConnectCard
|
||||
serviceType="ICLOUD_PHOTOS"
|
||||
userId={userId}
|
||||
isLinked={iosConnected}
|
||||
onLinked={() => setIosConnected(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{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" value={nasHost} onChangeText={setNasHost} editable={!loading} placeholderTextColor={theme.textTertiary} />
|
||||
|
||||
<Text style={[styles.fieldLabel, { color: theme.textSecondary }]}>Port</Text>
|
||||
<TextInput style={[styles.fieldInput, { backgroundColor: theme.inputBg, borderColor: theme.border, color: theme.text }]}
|
||||
placeholder="9500" value={nasPort} onChangeText={setNasPort} editable={!loading} keyboardType="numeric" placeholderTextColor={theme.textTertiary} />
|
||||
|
||||
<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" value={nasUsername} onChangeText={setNasUsername} editable={!loading} autoCapitalize="none" placeholderTextColor={theme.textTertiary} />
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.authButton, { backgroundColor: theme.accent }, loading && styles.buttonDisabled]}
|
||||
onPress={handleNasAuth} disabled={loading}
|
||||
>
|
||||
{loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.authButtonText}>Test & Connect</Text>}
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
<ServiceConnectCard
|
||||
serviceType="NAS"
|
||||
userId={userId}
|
||||
isLinked={nasConnected}
|
||||
onLinked={() => setNasConnected(true)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
@@ -277,7 +141,6 @@ const OnboardingScreen = ({ navigation, route }) => {
|
||||
<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}
|
||||
@@ -286,8 +149,7 @@ const OnboardingScreen = ({ navigation, route }) => {
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={[
|
||||
styles.checkbox,
|
||||
{ borderColor: theme.primary },
|
||||
styles.checkbox, { borderColor: theme.primary },
|
||||
autoBackupServices.includes(svc.type) && { backgroundColor: theme.primary },
|
||||
]}>
|
||||
{autoBackupServices.includes(svc.type) && <Text style={styles.checkmark}>✓</Text>}
|
||||
@@ -296,15 +158,13 @@ const OnboardingScreen = ({ navigation, route }) => {
|
||||
<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 },
|
||||
styles.checkbox, { borderColor: theme.border },
|
||||
autoBackupServices.length === 0 && { backgroundColor: theme.primary, borderColor: theme.primary },
|
||||
]}>
|
||||
{autoBackupServices.length === 0 && <Text style={styles.checkmark}>✓</Text>}
|
||||
@@ -372,7 +232,6 @@ const styles = StyleSheet.create({
|
||||
title: { fontSize: 26, fontWeight: '700', marginBottom: 6 },
|
||||
description: { fontSize: 14 },
|
||||
cardsContainer: { gap: 12 },
|
||||
// Toggle cards (Step 0)
|
||||
toggleCard: {
|
||||
borderRadius: 12, padding: 16, flexDirection: 'row',
|
||||
justifyContent: 'space-between', alignItems: 'center', borderWidth: 1.5,
|
||||
@@ -382,19 +241,7 @@ const styles = StyleSheet.create({
|
||||
toggleInfo: { flex: 1 },
|
||||
toggleName: { fontSize: 15, fontWeight: '600', marginBottom: 2 },
|
||||
toggleDescription: { fontSize: 12 },
|
||||
// Config cards (Step 1)
|
||||
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: 8 },
|
||||
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,
|
||||
},
|
||||
// Auto-backup (Step 2)
|
||||
checklistInfo: { fontSize: 13, marginBottom: 8 },
|
||||
checkRow: {
|
||||
flexDirection: 'row', alignItems: 'center', paddingVertical: 14,
|
||||
@@ -407,7 +254,6 @@ const styles = StyleSheet.create({
|
||||
checkmark: { color: '#fff', fontSize: 14, fontWeight: '700' },
|
||||
checkIcon: { fontSize: 18, marginRight: 8 },
|
||||
checkLabel: { fontSize: 14, fontWeight: '500', flex: 1 },
|
||||
// Footer
|
||||
footer: {
|
||||
flexDirection: 'row', paddingHorizontal: 20, paddingVertical: 14,
|
||||
borderTopWidth: 1, gap: 12,
|
||||
|
||||
Reference in new issue
Block a user