diff --git a/assets/adaptive-icon.png b/assets/adaptive-icon.png index 03d6f6b..1a56727 100644 Binary files a/assets/adaptive-icon.png and b/assets/adaptive-icon.png differ diff --git a/assets/splash-icon.png b/assets/splash-icon.png index 03d6f6b..696b2e2 100644 Binary files a/assets/splash-icon.png and b/assets/splash-icon.png differ diff --git a/src/components/PhotoGrid.js b/src/components/PhotoGrid.js index 0e2e4f5..6442285 100644 --- a/src/components/PhotoGrid.js +++ b/src/components/PhotoGrid.js @@ -14,6 +14,8 @@ const PhotoGrid = ({ refreshControl, ListHeaderComponent, ListEmptyComponent, + ListFooterComponent, + onEndReached, }) => { const { theme } = useTheme(); const itemSize = (width - GRID_GAP * (columns + 1)) / columns; @@ -55,6 +57,9 @@ const PhotoGrid = ({ refreshControl={refreshControl} ListHeaderComponent={ListHeaderComponent} ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + onEndReached={onEndReached} + onEndReachedThreshold={0.5} /> ); }; diff --git a/src/screens/HomeScreen.js b/src/screens/HomeScreen.js index 0e07533..bb376d8 100644 --- a/src/screens/HomeScreen.js +++ b/src/screens/HomeScreen.js @@ -24,6 +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 [loadingMore, setLoadingMore] = useState(false); useFocusEffect( useCallback(() => { @@ -54,29 +56,39 @@ const HomeScreen = ({ navigation, route }) => { const mediaFromDb = await DatabaseService.getMediaByUser(userId); const newMedia = []; - // Google Photos fetch + // 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 = googleAccount.account_data?.accessToken; - googleAuth.refreshToken = googleAccount.account_data?.refreshToken; - googleAuth.expiresAt = Date.now() + 3600000; - const photos = await googleAuth.getPhotos(50); + 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); } } - // iCloud fetch (placeholder) + // iCloud (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); - } + console.log('iCloud: placeholder — would fetch photos here'); } // NAS fetch @@ -114,6 +126,34 @@ const HomeScreen = ({ navigation, route }) => { setFilteredMedia(filtered); }; + const loadMorePhotos = async () => { + if (loadingMore || !googleNextPage) 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); + } + } catch (err) { + console.error('Error loading more photos:', err); + } finally { + setLoadingMore(false); + } + }; + const getServiceIcon = (type) => ({ GOOGLE_PHOTOS: '📷', ICLOUD_PHOTOS: '🍎', NAS: '💾' }[type] || '📱'); const getServiceName = (type) => ({ GOOGLE_PHOTOS: 'Google Photos', ICLOUD_PHOTOS: 'iCloud', NAS: 'NAS' }[type] || type); @@ -127,7 +167,7 @@ const HomeScreen = ({ navigation, route }) => { } const FilterChips = () => ( - + setSelectedFilter('all')} @@ -178,9 +218,15 @@ const HomeScreen = ({ navigation, route }) => { { setSelectedMedia(item); setViewerVisible(true); }} + onEndReached={loadMorePhotos} refreshControl={ { setRefreshing(true); loadAllMedia(); }} /> } + ListFooterComponent={loadingMore ? ( + + + + ) : null} /> )} @@ -199,10 +245,11 @@ const styles = StyleSheet.create({ centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 24 }, loadingText: { marginTop: 12, fontSize: 14 }, // Filter chips - filterRow: { paddingHorizontal: 12, paddingVertical: 6, gap: 6 }, + filterContainer: { maxHeight: 40 }, + filterRow: { paddingHorizontal: 12, paddingVertical: 6, gap: 6, alignItems: 'center' }, chip: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 10, paddingVertical: 4, - borderRadius: 12, gap: 4, + borderRadius: 16, gap: 4, height: 28, }, chipIcon: { fontSize: 11 }, chipText: { fontSize: 12, fontWeight: '500', color: '#3c4043' }, diff --git a/src/screens/OnboardingScreen.js b/src/screens/OnboardingScreen.js index 2a0817c..d34fde8 100644 --- a/src/screens/OnboardingScreen.js +++ b/src/screens/OnboardingScreen.js @@ -65,6 +65,7 @@ const OnboardingScreen = ({ navigation, route }) => { await DatabaseService.linkAccount(userId, 'GOOGLE_PHOTOS', { accessToken: result.accessToken, refreshToken: result.refreshToken || null, + expiresAt: googleAuth.expiresAt || Date.now() + 3600000, email: result.email || 'google-user', }); diff --git a/src/screens/SettingsScreen.js b/src/screens/SettingsScreen.js index 527e635..788a928 100644 --- a/src/screens/SettingsScreen.js +++ b/src/screens/SettingsScreen.js @@ -153,6 +153,7 @@ const SettingsScreen = ({ navigation, route }) => { await DatabaseService.linkAccount(userId, 'GOOGLE_PHOTOS', { accessToken: result.accessToken, refreshToken: result.refreshToken || null, + expiresAt: gAuth.expiresAt || Date.now() + 3600000, email: result.email || 'google-user', }); Alert.alert('Success', 'Google Photos re-authenticated!');