mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-07 19:40:20 +00:00
217 lines
8.2 KiB
JavaScript
217 lines
8.2 KiB
JavaScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import {
|
|
StyleSheet, View, Text, ActivityIndicator, ScrollView, RefreshControl,
|
|
} from 'react-native';
|
|
import { useFocusEffect } from '@react-navigation/native';
|
|
import { useTheme } from '../services/ThemeContext';
|
|
import DatabaseService from '../services/DatabaseService';
|
|
import GoogleAUTH from '../AUTH/GoogleAUTH';
|
|
import NASAUTH from '../AUTH/NASAUTH';
|
|
import TopBar from '../components/TopBar';
|
|
import PhotoGrid from '../components/PhotoGrid';
|
|
import MediaViewer from '../components/MediaViewer';
|
|
import { TouchableOpacity } from 'react-native';
|
|
|
|
const HomeScreen = ({ navigation, route }) => {
|
|
const userId = route?.params?.userId;
|
|
const { theme } = useTheme();
|
|
const [currentUser, setCurrentUser] = useState(null);
|
|
const [linkedAccounts, setLinkedAccounts] = useState([]);
|
|
const [allMedia, setAllMedia] = useState([]);
|
|
const [filteredMedia, setFilteredMedia] = useState([]);
|
|
const [selectedFilter, setSelectedFilter] = useState('all');
|
|
const [loading, setLoading] = useState(true);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
const [selectedMedia, setSelectedMedia] = useState(null);
|
|
const [viewerVisible, setViewerVisible] = useState(false);
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
if (userId) loadUserData();
|
|
}, [userId])
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (currentUser) loadAllMedia();
|
|
}, [currentUser]);
|
|
|
|
useEffect(() => { filterMedia(); }, [selectedFilter, allMedia]);
|
|
|
|
const loadUserData = async () => {
|
|
try {
|
|
const user = await DatabaseService.getUserById(userId);
|
|
setCurrentUser(user);
|
|
const accounts = await DatabaseService.getLinkedAccounts(userId);
|
|
setLinkedAccounts(accounts);
|
|
} catch (err) {
|
|
console.error('Error loading user data:', err);
|
|
}
|
|
};
|
|
|
|
const loadAllMedia = async () => {
|
|
if (!loading) setLoading(true);
|
|
try {
|
|
const mediaFromDb = await DatabaseService.getMediaByUser(userId);
|
|
const newMedia = [];
|
|
|
|
// Google Photos fetch
|
|
const googleAccount = linkedAccounts.find((a) => a.service_type === 'GOOGLE_PHOTOS');
|
|
if (googleAccount) {
|
|
try {
|
|
const googleAuth = new GoogleAUTH();
|
|
googleAuth.accessToken = googleAccount.account_data?.accessToken;
|
|
googleAuth.refreshToken = googleAccount.account_data?.refreshToken;
|
|
googleAuth.expiresAt = Date.now() + 3600000;
|
|
const photos = await googleAuth.getPhotos(50);
|
|
if (photos?.mediaItems?.length) newMedia.push(...photos.mediaItems);
|
|
} catch (err) {
|
|
console.error('Google Photos fetch error:', err);
|
|
}
|
|
}
|
|
|
|
// iCloud fetch (placeholder)
|
|
const iosAccount = linkedAccounts.find((a) => a.service_type === 'ICLOUD_PHOTOS');
|
|
if (iosAccount) {
|
|
try {
|
|
console.log('iCloud: placeholder - would fetch photos here');
|
|
} catch (err) {
|
|
console.error('iCloud fetch error:', err);
|
|
}
|
|
}
|
|
|
|
// NAS fetch
|
|
const nasAccount = linkedAccounts.find((a) => a.service_type === 'NAS');
|
|
if (nasAccount) {
|
|
try {
|
|
const nasAuth = new NASAUTH();
|
|
const nasData = nasAccount.account_data;
|
|
await nasAuth.authenticate(nasData.host, nasData.port, nasData.username, nasData.password, nasData.photoFolder);
|
|
const nasPhotos = await nasAuth.getPhotos(nasData.photoFolder);
|
|
if (nasPhotos?.mediaItems) newMedia.push(...nasPhotos.mediaItems);
|
|
await nasAuth.revokeAccess();
|
|
} catch (err) {
|
|
console.error('NAS fetch error:', err);
|
|
}
|
|
}
|
|
|
|
const combined = [...mediaFromDb, ...newMedia];
|
|
const unique = Array.from(new Map(combined.map((m) => [m.source_id || m.id || m.path, m])).values());
|
|
setAllMedia(unique);
|
|
} catch (err) {
|
|
console.error('Error loading media:', err);
|
|
} finally {
|
|
setLoading(false);
|
|
setRefreshing(false);
|
|
}
|
|
};
|
|
|
|
const filterMedia = () => {
|
|
let filtered = allMedia;
|
|
if (selectedFilter !== 'all') {
|
|
filtered = allMedia.filter((m) => m.source_service === selectedFilter);
|
|
}
|
|
filtered.sort((a, b) => new Date(b.created_date || 0) - new Date(a.created_date || 0));
|
|
setFilteredMedia(filtered);
|
|
};
|
|
|
|
const getServiceIcon = (type) => ({ GOOGLE_PHOTOS: '📷', ICLOUD_PHOTOS: '🍎', NAS: '💾' }[type] || '📱');
|
|
const getServiceName = (type) => ({ GOOGLE_PHOTOS: 'Google Photos', ICLOUD_PHOTOS: 'iCloud', NAS: 'NAS' }[type] || type);
|
|
|
|
if (loading && allMedia.length === 0) {
|
|
return (
|
|
<View style={[styles.centerContainer, { backgroundColor: theme.background }]}>
|
|
<ActivityIndicator size="large" color={theme.primary} />
|
|
<Text style={[styles.loadingText, { color: theme.textSecondary }]}>Loading photos...</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const FilterChips = () => (
|
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.filterRow}>
|
|
<TouchableOpacity
|
|
style={[styles.chip, { backgroundColor: selectedFilter === 'all' ? theme.chipActiveBg : theme.chipBg }]}
|
|
onPress={() => setSelectedFilter('all')}
|
|
>
|
|
<Text style={[styles.chipText, selectedFilter === 'all' && { color: theme.primary, fontWeight: '600' }]}>All</Text>
|
|
</TouchableOpacity>
|
|
{linkedAccounts.map((acc) => (
|
|
<TouchableOpacity
|
|
key={acc.id}
|
|
style={[styles.chip, { backgroundColor: selectedFilter === acc.service_type ? theme.chipActiveBg : theme.chipBg }]}
|
|
onPress={() => setSelectedFilter(acc.service_type)}
|
|
>
|
|
<Text style={styles.chipIcon}>{getServiceIcon(acc.service_type)}</Text>
|
|
<Text style={[styles.chipText, selectedFilter === acc.service_type && { color: theme.primary, fontWeight: '600' }]}>
|
|
{getServiceName(acc.service_type)}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</ScrollView>
|
|
);
|
|
|
|
return (
|
|
<View style={[styles.container, { backgroundColor: theme.background }]}>
|
|
<TopBar
|
|
currentUser={currentUser}
|
|
onSearchPress={() => navigation.navigate('Search')}
|
|
onProfilePress={() => navigation.navigate('Settings', { userId })}
|
|
onMenuPress={() => navigation.navigate('Settings', { userId })}
|
|
/>
|
|
|
|
{linkedAccounts.length > 0 && <FilterChips />}
|
|
|
|
{filteredMedia.length === 0 ? (
|
|
<View style={styles.centerContainer}>
|
|
<Text style={{ fontSize: 56, marginBottom: 16 }}>📸</Text>
|
|
<Text style={[styles.emptyTitle, { color: theme.text }]}>No Photos Yet</Text>
|
|
<Text style={[styles.emptyText, { color: theme.textSecondary }]}>
|
|
Photos from your connected services will appear here
|
|
</Text>
|
|
<TouchableOpacity
|
|
style={[styles.retryButton, { backgroundColor: theme.primary }]}
|
|
onPress={() => { setRefreshing(true); loadAllMedia(); }}
|
|
>
|
|
<Text style={styles.retryButtonText}>Refresh</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
) : (
|
|
<PhotoGrid
|
|
data={filteredMedia}
|
|
onItemPress={(item) => { setSelectedMedia(item); setViewerVisible(true); }}
|
|
refreshControl={
|
|
<RefreshControl refreshing={refreshing} onRefresh={() => { setRefreshing(true); loadAllMedia(); }} />
|
|
}
|
|
/>
|
|
)}
|
|
|
|
<MediaViewer
|
|
visible={viewerVisible}
|
|
media={selectedMedia}
|
|
onClose={() => setViewerVisible(false)}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1 },
|
|
centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 24 },
|
|
loadingText: { marginTop: 12, fontSize: 14 },
|
|
// Filter chips
|
|
filterRow: { paddingHorizontal: 12, paddingVertical: 8, gap: 8 },
|
|
chip: {
|
|
flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 7,
|
|
borderRadius: 20, gap: 6,
|
|
},
|
|
chipIcon: { fontSize: 13 },
|
|
chipText: { fontSize: 13, fontWeight: '500', color: '#3c4043' },
|
|
// Empty
|
|
emptyTitle: { fontSize: 18, fontWeight: '600', marginBottom: 6 },
|
|
emptyText: { fontSize: 13, textAlign: 'center', marginBottom: 20 },
|
|
retryButton: { paddingHorizontal: 24, paddingVertical: 10, borderRadius: 20 },
|
|
retryButtonText: { color: '#fff', fontSize: 14, fontWeight: '600' },
|
|
});
|
|
|
|
export default HomeScreen;
|
|
|