mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-07 19:40:20 +00:00
feat: integrate device media service for local photo access and update service icons/names
This commit is contained in:
1 parent
e5dbdfeba3
commit
0ef59ba5d3
9 files changed
+313
-94
No files matched your search
+54
-66
@@ -5,7 +5,7 @@ import {
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { useTheme } from '../services/ThemeContext';
|
||||
import DatabaseService from '../services/DatabaseService';
|
||||
import GoogleAUTH from '../AUTH/GoogleAUTH';
|
||||
import DeviceMediaService from '../services/DeviceMediaService';
|
||||
import NASAUTH from '../AUTH/NASAUTH';
|
||||
import TopBar from '../components/TopBar';
|
||||
import PhotoGrid from '../components/PhotoGrid';
|
||||
@@ -24,7 +24,8 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [selectedMedia, setSelectedMedia] = useState(null);
|
||||
const [viewerVisible, setViewerVisible] = useState(false);
|
||||
const [googleNextPage, setGoogleNextPage] = useState(null);
|
||||
const [deviceEndCursor, setDeviceEndCursor] = useState(null);
|
||||
const [deviceHasMore, setDeviceHasMore] = useState(false);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
@@ -53,45 +54,21 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
const loadAllMedia = async () => {
|
||||
if (!loading) setLoading(true);
|
||||
try {
|
||||
const mediaFromDb = await DatabaseService.getMediaByUser(userId);
|
||||
const newMedia = [];
|
||||
|
||||
// Google Photos — restore tokens from the linked account and fetch
|
||||
const googleAccount = linkedAccounts.find((a) => a.service_type === 'GOOGLE_PHOTOS');
|
||||
if (googleAccount) {
|
||||
try {
|
||||
const data = googleAccount.account_data || {};
|
||||
const googleAuth = new GoogleAUTH();
|
||||
googleAuth.accessToken = data.accessToken || null;
|
||||
googleAuth.refreshToken = data.refreshToken || null;
|
||||
// Set expiresAt to 0 so getAccessToken() will refresh if needed
|
||||
googleAuth.expiresAt = data.expiresAt || 0;
|
||||
|
||||
const photos = await googleAuth.getPhotos(100);
|
||||
if (photos?.mediaItems?.length) newMedia.push(...photos.mediaItems);
|
||||
setGoogleNextPage(photos?.nextPageToken || null);
|
||||
|
||||
// Persist refreshed tokens back to DB if they changed
|
||||
if (googleAuth.accessToken && googleAuth.accessToken !== data.accessToken) {
|
||||
await DatabaseService.linkAccount(userId, 'GOOGLE_PHOTOS', {
|
||||
accessToken: googleAuth.accessToken,
|
||||
refreshToken: googleAuth.refreshToken || data.refreshToken,
|
||||
expiresAt: googleAuth.expiresAt,
|
||||
email: data.email || 'google-user',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Google Photos fetch error:', err);
|
||||
// ─── Device photos (camera roll + Google Photos synced images) ───
|
||||
try {
|
||||
const deviceResult = await DeviceMediaService.getPhotos(100);
|
||||
if (deviceResult.mediaItems.length) {
|
||||
newMedia.push(...deviceResult.mediaItems);
|
||||
}
|
||||
setDeviceEndCursor(deviceResult.endCursor);
|
||||
setDeviceHasMore(deviceResult.hasNextPage);
|
||||
} catch (err) {
|
||||
console.error('Device media fetch error:', err);
|
||||
}
|
||||
|
||||
// iCloud (placeholder)
|
||||
const iosAccount = linkedAccounts.find((a) => a.service_type === 'ICLOUD_PHOTOS');
|
||||
if (iosAccount) {
|
||||
console.log('iCloud: placeholder — would fetch photos here');
|
||||
}
|
||||
|
||||
// NAS fetch
|
||||
// ─── NAS fetch ───
|
||||
const nasAccount = linkedAccounts.find((a) => a.service_type === 'NAS');
|
||||
if (nasAccount) {
|
||||
try {
|
||||
@@ -106,8 +83,15 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const combined = [...mediaFromDb, ...newMedia];
|
||||
const unique = Array.from(new Map(combined.map((m) => [m.source_id || m.id || m.path, m])).values());
|
||||
// ─── iCloud (placeholder) ───
|
||||
const iosAccount = linkedAccounts.find((a) => a.service_type === 'ICLOUD_PHOTOS');
|
||||
if (iosAccount) {
|
||||
console.log('iCloud: placeholder — would fetch photos here');
|
||||
}
|
||||
|
||||
const unique = Array.from(
|
||||
new Map(newMedia.map((m) => [m.source_id || m.id || m.local_path, m])).values()
|
||||
);
|
||||
setAllMedia(unique);
|
||||
} catch (err) {
|
||||
console.error('Error loading media:', err);
|
||||
@@ -127,26 +111,20 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
};
|
||||
|
||||
const loadMorePhotos = async () => {
|
||||
if (loadingMore || !googleNextPage) return;
|
||||
if (loadingMore || !deviceHasMore) return;
|
||||
setLoadingMore(true);
|
||||
try {
|
||||
const googleAccount = linkedAccounts.find((a) => a.service_type === 'GOOGLE_PHOTOS');
|
||||
if (googleAccount) {
|
||||
const data = googleAccount.account_data || {};
|
||||
const googleAuth = new GoogleAUTH();
|
||||
googleAuth.accessToken = data.accessToken || null;
|
||||
googleAuth.refreshToken = data.refreshToken || null;
|
||||
googleAuth.expiresAt = data.expiresAt || 0;
|
||||
|
||||
const photos = await googleAuth.getPhotos(100, googleNextPage);
|
||||
if (photos?.mediaItems?.length) {
|
||||
setAllMedia((prev) => {
|
||||
const combined = [...prev, ...photos.mediaItems];
|
||||
return Array.from(new Map(combined.map((m) => [m.source_id || m.id || m.path, m])).values());
|
||||
});
|
||||
}
|
||||
setGoogleNextPage(photos?.nextPageToken || null);
|
||||
const result = await DeviceMediaService.getPhotos(100, deviceEndCursor);
|
||||
if (result.mediaItems.length) {
|
||||
setAllMedia((prev) => {
|
||||
const combined = [...prev, ...result.mediaItems];
|
||||
return Array.from(
|
||||
new Map(combined.map((m) => [m.source_id || m.id || m.local_path, m])).values()
|
||||
);
|
||||
});
|
||||
}
|
||||
setDeviceEndCursor(result.endCursor);
|
||||
setDeviceHasMore(result.hasNextPage);
|
||||
} catch (err) {
|
||||
console.error('Error loading more photos:', err);
|
||||
} finally {
|
||||
@@ -154,8 +132,8 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getServiceIcon = (type) => ({ GOOGLE_PHOTOS: '📷', ICLOUD_PHOTOS: '🍎', NAS: '💾' }[type] || '📱');
|
||||
const getServiceName = (type) => ({ GOOGLE_PHOTOS: 'Google Photos', ICLOUD_PHOTOS: 'iCloud', NAS: 'NAS' }[type] || type);
|
||||
const getServiceIcon = (type) => ({ DEVICE: '📱', GOOGLE_PHOTOS: '📷', ICLOUD_PHOTOS: '🍎', NAS: '💾' }[type] || '📱');
|
||||
const getServiceName = (type) => ({ DEVICE: 'On Device', GOOGLE_PHOTOS: 'Google Photos', ICLOUD_PHOTOS: 'iCloud', NAS: 'NAS' }[type] || type);
|
||||
|
||||
if (loading && allMedia.length === 0) {
|
||||
return (
|
||||
@@ -166,6 +144,16 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Build the list of available source filters
|
||||
const sourceFilters = [
|
||||
{ key: 'DEVICE', icon: '📱', name: 'On Device' },
|
||||
...linkedAccounts.map((acc) => ({
|
||||
key: acc.service_type,
|
||||
icon: getServiceIcon(acc.service_type),
|
||||
name: getServiceName(acc.service_type),
|
||||
})),
|
||||
];
|
||||
|
||||
const FilterChips = () => (
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.filterRow} style={styles.filterContainer}>
|
||||
<TouchableOpacity
|
||||
@@ -174,15 +162,15 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
>
|
||||
<Text style={[styles.chipText, selectedFilter === 'all' && { color: theme.primary, fontWeight: '600' }]}>All</Text>
|
||||
</TouchableOpacity>
|
||||
{linkedAccounts.map((acc) => (
|
||||
{sourceFilters.map((f) => (
|
||||
<TouchableOpacity
|
||||
key={acc.id}
|
||||
style={[styles.chip, { backgroundColor: selectedFilter === acc.service_type ? theme.chipActiveBg : theme.chipBg }]}
|
||||
onPress={() => setSelectedFilter(acc.service_type)}
|
||||
key={f.key}
|
||||
style={[styles.chip, { backgroundColor: selectedFilter === f.key ? theme.chipActiveBg : theme.chipBg }]}
|
||||
onPress={() => setSelectedFilter(f.key)}
|
||||
>
|
||||
<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 style={styles.chipIcon}>{f.icon}</Text>
|
||||
<Text style={[styles.chipText, selectedFilter === f.key && { color: theme.primary, fontWeight: '600' }]}>
|
||||
{f.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
@@ -198,14 +186,14 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
onMenuPress={() => navigation.navigate('Settings', { userId })}
|
||||
/>
|
||||
|
||||
{linkedAccounts.length > 0 && <FilterChips />}
|
||||
<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
|
||||
Grant media library permission or take some photos to get started
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.retryButton, { backgroundColor: theme.primary }]}
|
||||
|
||||
Reference in new issue
Block a user