mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-08 03:40:20 +00:00
added service health indicators for Google Photos, iCloud, and NAS; implemented health check logic in settings and onboarding screens
This commit is contained in:
1 parent
6f4ec66e2e
commit
9519b32d68
3 files changed
+242
-39
No files matched your search
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Circular health indicator dot
|
||||||
|
*
|
||||||
|
* status values:
|
||||||
|
* 'inactive' → grey (not used / not connected)
|
||||||
|
* 'healthy' → green (connected and working)
|
||||||
|
* 'error' → red (connected but errors fetching data)
|
||||||
|
* 'checking' → amber (currently checking)
|
||||||
|
*/
|
||||||
|
const COLORS = {
|
||||||
|
inactive: '#9e9e9e',
|
||||||
|
healthy: '#34a853',
|
||||||
|
error: '#d93025',
|
||||||
|
checking: '#fbbc04',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ServiceHealthIndicator = ({ status = 'inactive', size = 10 }) => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.dot,
|
||||||
|
{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
borderRadius: size / 2,
|
||||||
|
backgroundColor: COLORS[status] || COLORS.inactive,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
dot: {
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ServiceHealthIndicator;
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ import DatabaseService from '../services/DatabaseService';
|
|||||||
import GoogleAUTH from '../AUTH/GoogleAUTH';
|
import GoogleAUTH from '../AUTH/GoogleAUTH';
|
||||||
import NASAUTH from '../AUTH/NASAUTH';
|
import NASAUTH from '../AUTH/NASAUTH';
|
||||||
import NASSetupInstructions from '../components/NASSetupInstructions';
|
import NASSetupInstructions from '../components/NASSetupInstructions';
|
||||||
|
import ServiceHealthIndicator from '../components/ServiceHealthIndicator';
|
||||||
|
|
||||||
const OnboardingScreen = ({ navigation, route }) => {
|
const OnboardingScreen = ({ navigation, route }) => {
|
||||||
const { userId } = route.params;
|
const { userId } = route.params;
|
||||||
@@ -25,6 +26,11 @@ const OnboardingScreen = ({ navigation, route }) => {
|
|||||||
const [iosConnected, setIosConnected] = useState(false);
|
const [iosConnected, setIosConnected] = useState(false);
|
||||||
const [nasConnected, setNasConnected] = 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
|
// NAS config
|
||||||
const [nasHost, setNasHost] = useState('');
|
const [nasHost, setNasHost] = useState('');
|
||||||
const [nasPort, setNasPort] = useState('22');
|
const [nasPort, setNasPort] = useState('22');
|
||||||
@@ -52,6 +58,7 @@ const OnboardingScreen = ({ navigation, route }) => {
|
|||||||
const handleGoogleAuth = async () => {
|
const handleGoogleAuth = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setGoogleHealth('checking');
|
||||||
const googleAuth = new GoogleAUTH();
|
const googleAuth = new GoogleAUTH();
|
||||||
const result = await googleAuth.authenticate();
|
const result = await googleAuth.authenticate();
|
||||||
|
|
||||||
@@ -62,9 +69,11 @@ const OnboardingScreen = ({ navigation, route }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setGoogleConnected(true);
|
setGoogleConnected(true);
|
||||||
|
setGoogleHealth('healthy');
|
||||||
Alert.alert('Success', 'Google Photos connected!');
|
Alert.alert('Success', 'Google Photos connected!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Google auth error:', error);
|
console.error('Google auth error:', error);
|
||||||
|
setGoogleHealth('error');
|
||||||
Alert.alert('Error', 'Failed to connect Google Photos: ' + error.message);
|
Alert.alert('Error', 'Failed to connect Google Photos: ' + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -74,14 +83,17 @@ const OnboardingScreen = ({ navigation, route }) => {
|
|||||||
const handleIosAuth = async () => {
|
const handleIosAuth = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setIosHealth('checking');
|
||||||
await DatabaseService.linkAccount(userId, 'ICLOUD_PHOTOS', {
|
await DatabaseService.linkAccount(userId, 'ICLOUD_PHOTOS', {
|
||||||
status: 'placeholder',
|
status: 'placeholder',
|
||||||
note: 'iCloud integration pending Apple API access',
|
note: 'iCloud integration pending Apple API access',
|
||||||
});
|
});
|
||||||
setIosConnected(true);
|
setIosConnected(true);
|
||||||
|
setIosHealth('healthy');
|
||||||
Alert.alert('Info', 'iCloud Photos registered. Full integration coming soon.');
|
Alert.alert('Info', 'iCloud Photos registered. Full integration coming soon.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('iCloud auth error:', error);
|
console.error('iCloud auth error:', error);
|
||||||
|
setIosHealth('error');
|
||||||
Alert.alert('Error', 'Failed to set up iCloud: ' + error.message);
|
Alert.alert('Error', 'Failed to set up iCloud: ' + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -95,6 +107,7 @@ const OnboardingScreen = ({ navigation, route }) => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setNasHealth('checking');
|
||||||
const nasAuth = new NASAUTH();
|
const nasAuth = new NASAUTH();
|
||||||
await nasAuth.authenticate(
|
await nasAuth.authenticate(
|
||||||
nasHost.trim(), parseInt(nasPort) || 22,
|
nasHost.trim(), parseInt(nasPort) || 22,
|
||||||
@@ -107,9 +120,11 @@ const OnboardingScreen = ({ navigation, route }) => {
|
|||||||
photoFolder: nasPhotoFolder.trim(),
|
photoFolder: nasPhotoFolder.trim(),
|
||||||
});
|
});
|
||||||
setNasConnected(true);
|
setNasConnected(true);
|
||||||
|
setNasHealth('healthy');
|
||||||
Alert.alert('Success', 'NAS connected!');
|
Alert.alert('Success', 'NAS connected!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('NAS auth error:', error);
|
console.error('NAS auth error:', error);
|
||||||
|
setNasHealth('error');
|
||||||
Alert.alert('Error', 'Failed to connect NAS: ' + error.message);
|
Alert.alert('Error', 'Failed to connect NAS: ' + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -187,6 +202,7 @@ const OnboardingScreen = ({ navigation, route }) => {
|
|||||||
{googleEnabled && (
|
{googleEnabled && (
|
||||||
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
||||||
<View style={styles.configHeader}>
|
<View style={styles.configHeader}>
|
||||||
|
<ServiceHealthIndicator status={googleHealth} size={10} />
|
||||||
<Text style={styles.configIcon}>📷</Text>
|
<Text style={styles.configIcon}>📷</Text>
|
||||||
<Text style={[styles.configTitle, { color: theme.text }]}>Google Photos</Text>
|
<Text style={[styles.configTitle, { color: theme.text }]}>Google Photos</Text>
|
||||||
{googleConnected && <Text style={[styles.connectedBadge, { color: theme.accent }]}>✓ Connected</Text>}
|
{googleConnected && <Text style={[styles.connectedBadge, { color: theme.accent }]}>✓ Connected</Text>}
|
||||||
@@ -205,6 +221,7 @@ const OnboardingScreen = ({ navigation, route }) => {
|
|||||||
{iosEnabled && (
|
{iosEnabled && (
|
||||||
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
||||||
<View style={styles.configHeader}>
|
<View style={styles.configHeader}>
|
||||||
|
<ServiceHealthIndicator status={iosHealth} size={10} />
|
||||||
<Text style={styles.configIcon}>🍎</Text>
|
<Text style={styles.configIcon}>🍎</Text>
|
||||||
<Text style={[styles.configTitle, { color: theme.text }]}>iCloud Photos</Text>
|
<Text style={[styles.configTitle, { color: theme.text }]}>iCloud Photos</Text>
|
||||||
{iosConnected && <Text style={[styles.connectedBadge, { color: theme.accent }]}>✓ Connected</Text>}
|
{iosConnected && <Text style={[styles.connectedBadge, { color: theme.accent }]}>✓ Connected</Text>}
|
||||||
@@ -223,6 +240,7 @@ const OnboardingScreen = ({ navigation, route }) => {
|
|||||||
{nasEnabled && (
|
{nasEnabled && (
|
||||||
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
<View style={[styles.configCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
||||||
<View style={styles.configHeader}>
|
<View style={styles.configHeader}>
|
||||||
|
<ServiceHealthIndicator status={nasHealth} size={10} />
|
||||||
<Text style={styles.configIcon}>💾</Text>
|
<Text style={styles.configIcon}>💾</Text>
|
||||||
<Text style={[styles.configTitle, { color: theme.text }]}>NAS Storage</Text>
|
<Text style={[styles.configTitle, { color: theme.text }]}>NAS Storage</Text>
|
||||||
{nasConnected && <Text style={[styles.connectedBadge, { color: theme.accent }]}>✓ Connected</Text>}
|
{nasConnected && <Text style={[styles.connectedBadge, { color: theme.accent }]}>✓ Connected</Text>}
|
||||||
|
|||||||
+183
-39
@@ -7,16 +7,21 @@ import { useFocusEffect } from '@react-navigation/native';
|
|||||||
import { useTheme } from '../services/ThemeContext';
|
import { useTheme } from '../services/ThemeContext';
|
||||||
import DatabaseService from '../services/DatabaseService';
|
import DatabaseService from '../services/DatabaseService';
|
||||||
import NASSetupInstructions from '../components/NASSetupInstructions';
|
import NASSetupInstructions from '../components/NASSetupInstructions';
|
||||||
|
import ServiceHealthIndicator from '../components/ServiceHealthIndicator';
|
||||||
import NASAUTH from '../AUTH/NASAUTH';
|
import NASAUTH from '../AUTH/NASAUTH';
|
||||||
|
import GoogleAUTH from '../AUTH/GoogleAUTH';
|
||||||
|
|
||||||
const SettingsScreen = ({ navigation, route }) => {
|
const SettingsScreen = ({ navigation, route }) => {
|
||||||
const userId = route?.params?.userId;
|
const userId = route?.params?.userId;
|
||||||
const { theme, themeName, setThemeName, themes } = useTheme();
|
const { theme, themeName, setThemeName } = useTheme();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [accounts, setAccounts] = useState([]);
|
const [accounts, setAccounts] = useState([]);
|
||||||
const [autoBackup, setAutoBackup] = useState([]);
|
const [autoBackup, setAutoBackup] = useState([]);
|
||||||
const [nasOffload, setNasOffload] = useState(false);
|
const [nasOffload, setNasOffload] = useState(false);
|
||||||
|
|
||||||
|
// Per-service health: { GOOGLE_PHOTOS: 'healthy'|'error'|'inactive', ... }
|
||||||
|
const [serviceHealth, setServiceHealth] = useState({});
|
||||||
|
|
||||||
// NAS edit fields
|
// NAS edit fields
|
||||||
const [editingNas, setEditingNas] = useState(false);
|
const [editingNas, setEditingNas] = useState(false);
|
||||||
const [nasHost, setNasHost] = useState('');
|
const [nasHost, setNasHost] = useState('');
|
||||||
@@ -45,20 +50,131 @@ const SettingsScreen = ({ navigation, route }) => {
|
|||||||
setNasUsername(nasAcc.account_data.username || '');
|
setNasUsername(nasAcc.account_data.username || '');
|
||||||
setNasPhotoFolder(nasAcc.account_data.photoFolder || '/home/user/photos');
|
setNasPhotoFolder(nasAcc.account_data.photoFolder || '/home/user/photos');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check health for each enabled account
|
||||||
|
checkAllServiceHealth(allAccounts);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error loading settings:', err);
|
console.error('Error loading settings:', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ─── Health checks ─────────────────────────────────────────
|
||||||
|
const checkAllServiceHealth = async (accs) => {
|
||||||
|
const health = {};
|
||||||
|
for (const acc of accs) {
|
||||||
|
if (!acc.is_enabled) {
|
||||||
|
health[acc.service_type] = 'inactive';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
health[acc.service_type] = 'checking';
|
||||||
|
setServiceHealth((prev) => ({ ...prev, [acc.service_type]: 'checking' }));
|
||||||
|
health[acc.service_type] = await checkSingleHealth(acc);
|
||||||
|
}
|
||||||
|
setServiceHealth(health);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkSingleHealth = async (acc) => {
|
||||||
|
try {
|
||||||
|
if (acc.service_type === 'GOOGLE_PHOTOS') {
|
||||||
|
const gAuth = new GoogleAUTH();
|
||||||
|
gAuth.accessToken = acc.account_data?.accessToken;
|
||||||
|
gAuth.refreshToken = acc.account_data?.refreshToken;
|
||||||
|
gAuth.expiresAt = Date.now() + 3600000;
|
||||||
|
const res = await gAuth.getPhotos(1);
|
||||||
|
return res?.mediaItems !== undefined ? 'healthy' : 'error';
|
||||||
|
}
|
||||||
|
if (acc.service_type === 'NAS') {
|
||||||
|
const nasAuth = new NASAUTH();
|
||||||
|
const d = acc.account_data || {};
|
||||||
|
await nasAuth.authenticate(d.host, d.port, d.username, d.password, d.photoFolder);
|
||||||
|
await nasAuth.revokeAccess();
|
||||||
|
return 'healthy';
|
||||||
|
}
|
||||||
|
if (acc.service_type === 'ICLOUD_PHOTOS') {
|
||||||
|
// Placeholder – always "healthy" since it's a stub
|
||||||
|
return 'healthy';
|
||||||
|
}
|
||||||
|
return 'inactive';
|
||||||
|
} catch {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const recheckHealth = async (acc) => {
|
||||||
|
setServiceHealth((prev) => ({ ...prev, [acc.service_type]: 'checking' }));
|
||||||
|
const status = await checkSingleHealth(acc);
|
||||||
|
setServiceHealth((prev) => ({ ...prev, [acc.service_type]: status }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Service actions ───────────────────────────────────────
|
||||||
const toggleService = async (accountId, currentEnabled) => {
|
const toggleService = async (accountId, currentEnabled) => {
|
||||||
try {
|
try {
|
||||||
await DatabaseService.toggleAccountEnabled(accountId, !currentEnabled);
|
await DatabaseService.toggleAccountEnabled(accountId, !currentEnabled);
|
||||||
await loadData();
|
await loadData();
|
||||||
} catch (err) {
|
} catch {
|
||||||
Alert.alert('Error', 'Failed to update service');
|
Alert.alert('Error', 'Failed to update service');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteAccount = (acc) => {
|
||||||
|
Alert.alert(
|
||||||
|
'Delete Service',
|
||||||
|
`Remove ${getServiceName(acc.service_type)} from your account? This cannot be undone.`,
|
||||||
|
[
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Delete',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: async () => {
|
||||||
|
try {
|
||||||
|
await DatabaseService.removeLinkedAccount(acc.id);
|
||||||
|
setServiceHealth((prev) => {
|
||||||
|
const next = { ...prev };
|
||||||
|
delete next[acc.service_type];
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
await loadData();
|
||||||
|
Alert.alert('Deleted', `${getServiceName(acc.service_type)} has been removed.`);
|
||||||
|
} catch {
|
||||||
|
Alert.alert('Error', 'Failed to remove service');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const reauthenticateService = async (acc) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
if (acc.service_type === 'GOOGLE_PHOTOS') {
|
||||||
|
const gAuth = new GoogleAUTH();
|
||||||
|
const result = await gAuth.authenticate();
|
||||||
|
await DatabaseService.linkAccount(userId, 'GOOGLE_PHOTOS', {
|
||||||
|
accessToken: result.accessToken,
|
||||||
|
refreshToken: result.refreshToken || null,
|
||||||
|
email: result.email || 'google-user',
|
||||||
|
});
|
||||||
|
Alert.alert('Success', 'Google Photos re-authenticated!');
|
||||||
|
} else if (acc.service_type === 'ICLOUD_PHOTOS') {
|
||||||
|
await DatabaseService.linkAccount(userId, 'ICLOUD_PHOTOS', {
|
||||||
|
status: 'placeholder',
|
||||||
|
note: 'iCloud integration pending Apple API access',
|
||||||
|
});
|
||||||
|
Alert.alert('Info', 'iCloud Photos re-registered (placeholder).');
|
||||||
|
} else if (acc.service_type === 'NAS') {
|
||||||
|
setEditingNas(true);
|
||||||
|
setLoading(false);
|
||||||
|
return; // user edits inline
|
||||||
|
}
|
||||||
|
await loadData();
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('Error', 'Re-authentication failed: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toggleAutoBackup = async (serviceType) => {
|
const toggleAutoBackup = async (serviceType) => {
|
||||||
const updated = autoBackup.includes(serviceType)
|
const updated = autoBackup.includes(serviceType)
|
||||||
? autoBackup.filter((s) => s !== serviceType)
|
? autoBackup.filter((s) => s !== serviceType)
|
||||||
@@ -130,7 +246,7 @@ const SettingsScreen = ({ navigation, route }) => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView style={styles.scrollContent} showsVerticalScrollIndicator={false}>
|
<ScrollView style={styles.scrollContent} showsVerticalScrollIndicator={false}>
|
||||||
{/* Theme Section */}
|
{/* ── Theme ──────────────────────────────────────── */}
|
||||||
<Text style={[styles.sectionTitle, { color: theme.textSecondary }]}>APPEARANCE</Text>
|
<Text style={[styles.sectionTitle, { color: theme.textSecondary }]}>APPEARANCE</Text>
|
||||||
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
||||||
{themeOptions.map((opt) => (
|
{themeOptions.map((opt) => (
|
||||||
@@ -153,7 +269,7 @@ const SettingsScreen = ({ navigation, route }) => {
|
|||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Services Section */}
|
{/* ── Services ──────────────────────────────────── */}
|
||||||
<Text style={[styles.sectionTitle, { color: theme.textSecondary }]}>SERVICES</Text>
|
<Text style={[styles.sectionTitle, { color: theme.textSecondary }]}>SERVICES</Text>
|
||||||
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
||||||
{accounts.length === 0 ? (
|
{accounts.length === 0 ? (
|
||||||
@@ -163,36 +279,60 @@ const SettingsScreen = ({ navigation, route }) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
accounts.map((acc) => (
|
accounts.map((acc) => {
|
||||||
<View
|
const health = serviceHealth[acc.service_type] || (acc.is_enabled ? 'checking' : 'inactive');
|
||||||
key={acc.id}
|
return (
|
||||||
style={[styles.serviceRow, { borderBottomColor: theme.borderLight }]}
|
<View key={acc.id}>
|
||||||
>
|
{/* Main row */}
|
||||||
<Text style={styles.serviceIcon}>{getServiceIcon(acc.service_type)}</Text>
|
<View style={[styles.serviceRow, { borderBottomColor: theme.borderLight }]}>
|
||||||
<View style={styles.serviceInfo}>
|
<TouchableOpacity onPress={() => recheckHealth(acc)} activeOpacity={0.6}>
|
||||||
<Text style={[styles.serviceName, { color: theme.text }]}>
|
<ServiceHealthIndicator status={health} size={10} />
|
||||||
{getServiceName(acc.service_type)}
|
</TouchableOpacity>
|
||||||
</Text>
|
<Text style={styles.serviceIcon}>{getServiceIcon(acc.service_type)}</Text>
|
||||||
<Text style={[styles.serviceStatus, { color: acc.is_enabled ? theme.accent : theme.textTertiary }]}>
|
<View style={styles.serviceInfo}>
|
||||||
{acc.is_enabled ? 'Enabled' : 'Disabled'}
|
<Text style={[styles.serviceName, { color: theme.text }]}>
|
||||||
</Text>
|
{getServiceName(acc.service_type)}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.serviceStatus, { color: acc.is_enabled ? theme.accent : theme.textTertiary }]}>
|
||||||
|
{acc.is_enabled ? 'Enabled' : 'Disabled'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Switch
|
||||||
|
value={!!acc.is_enabled}
|
||||||
|
onValueChange={() => toggleService(acc.id, acc.is_enabled)}
|
||||||
|
trackColor={{ false: theme.border, true: '#a8dab5' }}
|
||||||
|
thumbColor={acc.is_enabled ? theme.accent : '#f4f3f4'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{/* Action buttons row */}
|
||||||
|
<View style={[styles.serviceActions, { borderBottomColor: theme.borderLight }]}>
|
||||||
|
{acc.service_type === 'NAS' && acc.is_enabled && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.actionBtn, { borderColor: theme.border }]}
|
||||||
|
onPress={() => setEditingNas(!editingNas)}
|
||||||
|
>
|
||||||
|
<Text style={[styles.actionBtnText, { color: theme.primary }]}>Edit</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.actionBtn, { borderColor: theme.border }]}
|
||||||
|
onPress={() => reauthenticateService(acc)}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
<Text style={[styles.actionBtnText, { color: theme.primary }]}>
|
||||||
|
{loading ? '...' : 'Re-auth'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.actionBtn, { borderColor: theme.danger }]}
|
||||||
|
onPress={() => deleteAccount(acc)}
|
||||||
|
>
|
||||||
|
<Text style={[styles.actionBtnText, { color: theme.danger }]}>Delete</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{acc.service_type === 'NAS' && acc.is_enabled && (
|
);
|
||||||
<TouchableOpacity
|
})
|
||||||
style={[styles.editButton, { borderColor: theme.border }]}
|
|
||||||
onPress={() => setEditingNas(!editingNas)}
|
|
||||||
>
|
|
||||||
<Text style={[styles.editButtonText, { color: theme.primary }]}>Edit</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
<Switch
|
|
||||||
value={!!acc.is_enabled}
|
|
||||||
onValueChange={() => toggleService(acc.id, acc.is_enabled)}
|
|
||||||
trackColor={{ false: theme.border, true: '#a8dab5' }}
|
|
||||||
thumbColor={acc.is_enabled ? theme.accent : '#f4f3f4'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
))
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -248,7 +388,7 @@ const SettingsScreen = ({ navigation, route }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Auto Backup */}
|
{/* ── Auto Backup ───────────────────────────────── */}
|
||||||
<Text style={[styles.sectionTitle, { color: theme.textSecondary }]}>AUTOMATIC BACKUP</Text>
|
<Text style={[styles.sectionTitle, { color: theme.textSecondary }]}>AUTOMATIC BACKUP</Text>
|
||||||
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
||||||
<Text style={[styles.cardSubtext, { color: theme.textSecondary }]}>
|
<Text style={[styles.cardSubtext, { color: theme.textSecondary }]}>
|
||||||
@@ -309,7 +449,7 @@ const SettingsScreen = ({ navigation, route }) => {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Sign Out */}
|
{/* ── Sign Out ──────────────────────────────────── */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.logoutButton, { borderColor: theme.danger }]}
|
style={[styles.logoutButton, { borderColor: theme.danger }]}
|
||||||
onPress={handleLogout}
|
onPress={handleLogout}
|
||||||
@@ -356,14 +496,18 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 12,
|
flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 12,
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
serviceIcon: { fontSize: 22, marginRight: 12 },
|
serviceIcon: { fontSize: 22, marginRight: 8 },
|
||||||
serviceInfo: { flex: 1 },
|
serviceInfo: { flex: 1 },
|
||||||
serviceName: { fontSize: 14, fontWeight: '600', marginBottom: 2 },
|
serviceName: { fontSize: 14, fontWeight: '600', marginBottom: 2 },
|
||||||
serviceStatus: { fontSize: 12 },
|
serviceStatus: { fontSize: 12 },
|
||||||
editButton: {
|
serviceActions: {
|
||||||
borderWidth: 1, borderRadius: 6, paddingHorizontal: 10, paddingVertical: 4, marginRight: 10,
|
flexDirection: 'row', paddingHorizontal: 14, paddingVertical: 8, gap: 8,
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
editButtonText: { fontSize: 12, fontWeight: '600' },
|
actionBtn: {
|
||||||
|
borderWidth: 1, borderRadius: 6, paddingHorizontal: 12, paddingVertical: 5,
|
||||||
|
},
|
||||||
|
actionBtnText: { fontSize: 12, fontWeight: '600' },
|
||||||
emptyRow: { padding: 16 },
|
emptyRow: { padding: 16 },
|
||||||
emptyText: { fontSize: 13, textAlign: 'center' },
|
emptyText: { fontSize: 13, textAlign: 'center' },
|
||||||
// NAS fields
|
// NAS fields
|
||||||
|
|||||||
Reference in new issue
Block a user